Compare commits
No commits in common. "main" and "v60.2" have entirely different histories.
21 changed files with 169 additions and 339 deletions
5
.gitattributes
vendored
5
.gitattributes
vendored
|
@ -1,5 +0,0 @@
|
|||
# All text should use Unix-style Line-endings
|
||||
* text eol=lf
|
||||
|
||||
# Except mta-sts.txt (RFC 8461)
|
||||
mta-sts.txt text eol=crlf
|
|
@ -1,12 +1,6 @@
|
|||
CHANGELOG
|
||||
=========
|
||||
|
||||
Version 60.1 (October 30, 2022)
|
||||
-------------------------------
|
||||
|
||||
* A setup issue where the DNS server nsd isn't running at the end of setup is (hopefully) fixed.
|
||||
* Nextcloud is updated to 23.0.10 (contacts to 4.2.2, calendar to 3.5.1).
|
||||
|
||||
Version 60 (October 11, 2022)
|
||||
-----------------------------
|
||||
|
||||
|
@ -23,7 +17,7 @@ No major features of Mail-in-a-Box have changed in this release, although some m
|
|||
With the newer version of Ubuntu the following software packages we use are updated:
|
||||
|
||||
* dovecot is upgraded to 2.3.16, postfix to 3.6.4, opendmark to 1.4 (which adds ARC-Authentication-Results headers), and spampd to 2.53 (alleviating a mail delivery rate limiting bug).
|
||||
* Nextcloud is upgraded to 23.0.4 (contacts to 4.2.0, calendar to 3.5.0).
|
||||
* Nextcloud is upgraded to 23.0.4.
|
||||
* Roundcube is upgraded to 1.6.0.
|
||||
* certbot is upgraded to 1.21 (via the Ubuntu repository instead of a PPA).
|
||||
* fail2ban is upgraded to 0.11.2.
|
||||
|
|
21
README.md
21
README.md
|
@ -1,6 +1,5 @@
|
|||
# Power Mail-in-a-Box
|
||||
## **[Installation](#installation)** (current version: v60.5)
|
||||
## **[Upgrading Quick Start](#upgrading)**
|
||||
**[Installation](#installation)** (current version: v60.2)
|
||||
|
||||
[](https://ko-fi.com/davness)
|
||||
|
||||
|
@ -81,21 +80,3 @@ sudo dpkg-reconfigure locales
|
|||
```
|
||||
curl -L https://power-mailinabox.net/setup.sh | sudo bash
|
||||
```
|
||||
|
||||
# Upgrading
|
||||
|
||||
To upgrade an existing box to the latest version, run the same command as you do to perform a new installation:
|
||||
|
||||
```
|
||||
curl -L https://power-mailinabox.net/setup.sh | sudo bash
|
||||
```
|
||||
|
||||
## Installing or upgrading to a different version
|
||||
If for some reason you wish to install a different version (for example, an older version for a workaround, or a beta/release candidate version for testing), you can use the following command.
|
||||
|
||||
```
|
||||
curl -L https://power-mailinabox.net/<VERSION>/setup.sh | sudo bash
|
||||
```
|
||||
Where `<VERSION>` is the version you want to install. (**Example:** `v60.0`).
|
||||
|
||||
> ⚠️ **Downgrading might not always be possible and is not supported!** Make sure you know what you're doing before doing so.
|
||||
|
|
3
Vagrantfile
vendored
3
Vagrantfile
vendored
|
@ -49,6 +49,9 @@ Vagrant.configure("2") do |config|
|
|||
m.vm.network "private_network", ip: "192.168.168.#{ip+n}"
|
||||
|
||||
m.vm.provision "shell", :inline => <<-SH
|
||||
# Make sure we have IPv6 loopback (::1)
|
||||
sysctl -w net.ipv6.conf.lo.disable_ipv6=0
|
||||
echo -e "fs.inotify.max_user_instances=1024\nnet.ipv6.conf.lo.disable_ipv6=0" > /etc/sysctl.conf
|
||||
git config --global --add safe.directory /vagrant
|
||||
|
||||
# Set environment variables so that the setup script does
|
||||
|
|
|
@ -15,7 +15,7 @@ info:
|
|||
license:
|
||||
name: CC0 1.0 Universal
|
||||
url: https://creativecommons.org/publicdomain/zero/1.0/legalcode
|
||||
version: 60.5
|
||||
version: 60.2
|
||||
x-logo:
|
||||
url: https://mailinabox.email/static/logo.png
|
||||
altText: Mail-in-a-Box logo
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
version: STSv1
|
||||
mode: MODE
|
||||
mx: PRIMARY_HOSTNAME
|
||||
max_age: 604800
|
||||
version: STSv1
|
||||
mode: MODE
|
||||
mx: PRIMARY_HOSTNAME
|
||||
max_age: 604800
|
||||
|
|
|
@ -240,9 +240,7 @@ def get_duplicity_target_url(config):
|
|||
# via get_duplicity_additional_args. Move the first part of the
|
||||
# path (the bucket name) into the hostname URL component, and leave
|
||||
# the rest for the path.
|
||||
target_bucket = target[2].lstrip('/').split('/', 1)
|
||||
target[1] = target_bucket[0]
|
||||
target[2] = target_bucket[1] if len(target_bucket) > 1 else ''
|
||||
target[1], target[2] = target[2].lstrip('/').split('/', 1)
|
||||
|
||||
target = urlunsplit(target)
|
||||
|
||||
|
|
|
@ -56,70 +56,71 @@ app = Flask(__name__,
|
|||
|
||||
# Decorator to protect views that require a user with 'admin' privileges.
|
||||
|
||||
def authorized_personnel_only(admin = True):
|
||||
def gatekeeper(viewfunc):
|
||||
|
||||
@wraps(viewfunc)
|
||||
def newview(*args, **kwargs):
|
||||
# Authenticate the passed credentials, which is either the API key or a username:password pair
|
||||
# and an optional X-Auth-Token token.
|
||||
error = None
|
||||
privs = []
|
||||
def authorized_personnel_only(viewfunc):
|
||||
|
||||
try:
|
||||
email, privs = auth_service.authenticate(request, env)
|
||||
@wraps(viewfunc)
|
||||
def newview(*args, **kwargs):
|
||||
# Authenticate the passed credentials, which is either the API key or a username:password pair
|
||||
# and an optional X-Auth-Token token.
|
||||
error = None
|
||||
privs = []
|
||||
|
||||
# Store the email address of the logged in user so it can be accessed
|
||||
# from the API methods that affect the calling user.
|
||||
request.user_email = email
|
||||
request.user_privs = privs
|
||||
try:
|
||||
email, privs = auth_service.authenticate(request, env)
|
||||
except ValueError as e:
|
||||
# Write a line in the log recording the failed login, unless no authorization header
|
||||
# was given which can happen on an initial request before a 403 response.
|
||||
if "Authorization" in request.headers:
|
||||
log_failed_login(request)
|
||||
|
||||
if not admin or "admin" in privs:
|
||||
return viewfunc(*args, **kwargs)
|
||||
else:
|
||||
error = "You are not an administrator."
|
||||
except ValueError as e:
|
||||
# Write a line in the log recording the failed login, unless no authorization header
|
||||
# was given which can happen on an initial request before a 403 response.
|
||||
if "Authorization" in request.headers:
|
||||
log_failed_login(request)
|
||||
# Authentication failed.
|
||||
error = str(e)
|
||||
|
||||
# Authentication failed.
|
||||
error = str(e)
|
||||
# Authorized to access an API view?
|
||||
if "admin" in privs:
|
||||
# Store the email address of the logged in user so it can be accessed
|
||||
# from the API methods that affect the calling user.
|
||||
request.user_email = email
|
||||
request.user_privs = privs
|
||||
|
||||
# Not authorized. Return a 401 (send auth) and a prompt to authorize by default.
|
||||
status = 401
|
||||
headers = {
|
||||
'WWW-Authenticate':
|
||||
'Basic realm="{0}"'.format(auth_service.auth_realm),
|
||||
'X-Reason': error,
|
||||
}
|
||||
# Call view func.
|
||||
return viewfunc(*args, **kwargs)
|
||||
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
# Don't issue a 401 to an AJAX request because the user will
|
||||
# be prompted for credentials, which is not helpful.
|
||||
status = 403
|
||||
headers = None
|
||||
if not error:
|
||||
error = "You are not an administrator."
|
||||
|
||||
if request.headers.get('Accept') in (None, "", "*/*"):
|
||||
# Return plain text output.
|
||||
return Response(error + "\n",
|
||||
status=status,
|
||||
mimetype='text/plain',
|
||||
headers=headers)
|
||||
else:
|
||||
# Return JSON output.
|
||||
return Response(json.dumps({
|
||||
"status": "error",
|
||||
"reason": error,
|
||||
}) + "\n",
|
||||
status=status,
|
||||
mimetype='application/json',
|
||||
headers=headers)
|
||||
# Not authorized. Return a 401 (send auth) and a prompt to authorize by default.
|
||||
status = 401
|
||||
headers = {
|
||||
'WWW-Authenticate':
|
||||
'Basic realm="{0}"'.format(auth_service.auth_realm),
|
||||
'X-Reason': error,
|
||||
}
|
||||
|
||||
return newview
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
# Don't issue a 401 to an AJAX request because the user will
|
||||
# be prompted for credentials, which is not helpful.
|
||||
status = 403
|
||||
headers = None
|
||||
|
||||
return gatekeeper
|
||||
if request.headers.get('Accept') in (None, "", "*/*"):
|
||||
# Return plain text output.
|
||||
return Response(error + "\n",
|
||||
status=status,
|
||||
mimetype='text/plain',
|
||||
headers=headers)
|
||||
else:
|
||||
# Return JSON output.
|
||||
return Response(json.dumps({
|
||||
"status": "error",
|
||||
"reason": error,
|
||||
}) + "\n",
|
||||
status=status,
|
||||
mimetype='application/json',
|
||||
headers=headers)
|
||||
|
||||
return newview
|
||||
|
||||
|
||||
@app.errorhandler(401)
|
||||
|
@ -212,7 +213,7 @@ def logout():
|
|||
|
||||
|
||||
@app.route('/mail/users')
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def mail_users():
|
||||
if request.args.get("format", "") == "json":
|
||||
return json_response(get_mail_users_ex(env, with_archived=True))
|
||||
|
@ -221,7 +222,7 @@ def mail_users():
|
|||
|
||||
|
||||
@app.route('/mail/users/add', methods=['POST'])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def mail_users_add():
|
||||
quota = request.form.get('quota', get_default_quota(env))
|
||||
try:
|
||||
|
@ -233,7 +234,7 @@ def mail_users_add():
|
|||
|
||||
|
||||
@app.route('/mail/users/quota', methods=['GET'])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def get_mail_users_quota():
|
||||
email = request.values.get('email', '')
|
||||
quota = get_mail_quota(email, env)
|
||||
|
@ -245,7 +246,7 @@ def get_mail_users_quota():
|
|||
|
||||
|
||||
@app.route('/mail/users/quota', methods=['POST'])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def mail_users_quota():
|
||||
try:
|
||||
return set_mail_quota(request.form.get('email', ''),
|
||||
|
@ -255,13 +256,8 @@ def mail_users_quota():
|
|||
|
||||
|
||||
@app.route('/mail/users/password', methods=['POST'])
|
||||
@authorized_personnel_only(admin = False)
|
||||
@authorized_personnel_only
|
||||
def mail_users_password():
|
||||
if "admin" not in request.user_privs:
|
||||
# Non-admins can only change their own password.
|
||||
if request.form.get('email', '') != request.user_email:
|
||||
return ("You are not an administrator; you can only change your own password!", 403)
|
||||
|
||||
try:
|
||||
return set_mail_password(request.form.get('email', ''),
|
||||
request.form.get('password', ''), env)
|
||||
|
@ -270,13 +266,13 @@ def mail_users_password():
|
|||
|
||||
|
||||
@app.route('/mail/users/remove', methods=['POST'])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def mail_users_remove():
|
||||
return remove_mail_user(request.form.get('email', ''), env)
|
||||
|
||||
|
||||
@app.route('/mail/users/privileges')
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def mail_user_privs():
|
||||
privs = get_mail_user_privileges(request.args.get('email', ''), env)
|
||||
if isinstance(privs, tuple):
|
||||
|
@ -285,7 +281,7 @@ def mail_user_privs():
|
|||
|
||||
|
||||
@app.route('/mail/users/privileges/add', methods=['POST'])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def mail_user_privs_add():
|
||||
return add_remove_mail_user_privilege(request.form.get('email', ''),
|
||||
request.form.get('privilege', ''),
|
||||
|
@ -293,7 +289,7 @@ def mail_user_privs_add():
|
|||
|
||||
|
||||
@app.route('/mail/users/privileges/remove', methods=['POST'])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def mail_user_privs_remove():
|
||||
return add_remove_mail_user_privilege(request.form.get('email', ''),
|
||||
request.form.get('privilege', ''),
|
||||
|
@ -301,7 +297,7 @@ def mail_user_privs_remove():
|
|||
|
||||
|
||||
@app.route('/mail/aliases')
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def mail_aliases():
|
||||
if request.args.get("format", "") == "json":
|
||||
return json_response(get_mail_aliases_ex(env))
|
||||
|
@ -312,7 +308,7 @@ def mail_aliases():
|
|||
|
||||
|
||||
@app.route('/mail/aliases/add', methods=['POST'])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def mail_aliases_add():
|
||||
return add_mail_alias(request.form.get('address', ''),
|
||||
request.form.get('forwards_to', ''),
|
||||
|
@ -323,13 +319,13 @@ def mail_aliases_add():
|
|||
|
||||
|
||||
@app.route('/mail/aliases/remove', methods=['POST'])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def mail_aliases_remove():
|
||||
return remove_mail_alias(request.form.get('address', ''), env)
|
||||
|
||||
|
||||
@app.route('/mail/domains')
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def mail_domains():
|
||||
return "".join(x + "\n" for x in get_mail_domains(env))
|
||||
|
||||
|
@ -338,14 +334,14 @@ def mail_domains():
|
|||
|
||||
|
||||
@app.route('/dns/zones')
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def dns_zones():
|
||||
from dns_update import get_dns_zones
|
||||
return json_response([z[0] for z in get_dns_zones(env)])
|
||||
|
||||
|
||||
@app.route('/dns/update', methods=['POST'])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def dns_update():
|
||||
from dns_update import do_dns_update
|
||||
try:
|
||||
|
@ -355,7 +351,7 @@ def dns_update():
|
|||
|
||||
|
||||
@app.route('/dns/secondary-nameserver')
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def dns_get_secondary_nameserver():
|
||||
from dns_update import get_custom_dns_config, get_secondary_dns
|
||||
return json_response({
|
||||
|
@ -365,7 +361,7 @@ def dns_get_secondary_nameserver():
|
|||
|
||||
|
||||
@app.route('/dns/secondary-nameserver', methods=['POST'])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def dns_set_secondary_nameserver():
|
||||
from dns_update import set_secondary_dns
|
||||
try:
|
||||
|
@ -379,7 +375,7 @@ def dns_set_secondary_nameserver():
|
|||
|
||||
|
||||
@app.route('/dns/custom')
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def dns_get_records(qname=None, rtype=None):
|
||||
# Get the current set of custom DNS records.
|
||||
from dns_update import get_custom_dns_config, get_dns_zones
|
||||
|
@ -435,7 +431,7 @@ def dns_get_records(qname=None, rtype=None):
|
|||
@app.route('/dns/custom/<qname>', methods=['GET', 'POST', 'PUT', 'DELETE'])
|
||||
@app.route('/dns/custom/<qname>/<rtype>',
|
||||
methods=['GET', 'POST', 'PUT', 'DELETE'])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def dns_set_record(qname, rtype="A"):
|
||||
from dns_update import do_dns_update, set_custom_dns_record
|
||||
try:
|
||||
|
@ -502,14 +498,14 @@ def dns_set_record(qname, rtype="A"):
|
|||
|
||||
|
||||
@app.route('/dns/dump')
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def dns_get_dump():
|
||||
from dns_update import build_recommended_dns
|
||||
return json_response(build_recommended_dns(env))
|
||||
|
||||
|
||||
@app.route('/dns/zonefile/<zone>')
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def dns_get_zonefile(zone):
|
||||
from dns_update import get_dns_zonefile
|
||||
return Response(get_dns_zonefile(zone, env),
|
||||
|
@ -521,7 +517,7 @@ def dns_get_zonefile(zone):
|
|||
|
||||
|
||||
@app.route('/ssl/status')
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def ssl_get_status():
|
||||
from ssl_certificates import get_certificates_to_provision
|
||||
from web_update import get_web_domains_info, get_web_domains
|
||||
|
@ -561,7 +557,7 @@ def ssl_get_status():
|
|||
|
||||
|
||||
@app.route('/ssl/csr/<domain>', methods=['POST'])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def ssl_get_csr(domain):
|
||||
from ssl_certificates import create_csr
|
||||
ssl_private_key = os.path.join(
|
||||
|
@ -571,7 +567,7 @@ def ssl_get_csr(domain):
|
|||
|
||||
|
||||
@app.route('/ssl/install', methods=['POST'])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def ssl_install_cert():
|
||||
from web_update import get_web_domains
|
||||
from ssl_certificates import install_cert
|
||||
|
@ -584,7 +580,7 @@ def ssl_install_cert():
|
|||
|
||||
|
||||
@app.route('/ssl/provision', methods=['POST'])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def ssl_provision_certs():
|
||||
from ssl_certificates import provision_certificates
|
||||
requests = provision_certificates(env, limit_domains=None)
|
||||
|
@ -595,7 +591,7 @@ def ssl_provision_certs():
|
|||
|
||||
|
||||
@app.route('/mfa/status', methods=['POST'])
|
||||
@authorized_personnel_only(admin = False)
|
||||
@authorized_personnel_only
|
||||
def mfa_get_status():
|
||||
# Anyone accessing this route is an admin, and we permit them to
|
||||
# see the MFA status for any user if they submit a 'user' form
|
||||
|
@ -603,9 +599,6 @@ def mfa_get_status():
|
|||
# only provision for themselves.
|
||||
# user field if given, otherwise the user making the request
|
||||
email = request.form.get('user', request.user_email)
|
||||
if "admin" not in request.user_privs and email != request.user_email:
|
||||
return ("You are not an administrator; you can only view your own MFA status!", 403)
|
||||
|
||||
try:
|
||||
resp = {"enabled_mfa": get_public_mfa_state(email, env)}
|
||||
if email == request.user_email:
|
||||
|
@ -616,7 +609,7 @@ def mfa_get_status():
|
|||
|
||||
|
||||
@app.route('/mfa/totp/enable', methods=['POST'])
|
||||
@authorized_personnel_only(admin = False)
|
||||
@authorized_personnel_only
|
||||
def totp_post_enable():
|
||||
secret = request.form.get('secret')
|
||||
token = request.form.get('token')
|
||||
|
@ -632,16 +625,13 @@ def totp_post_enable():
|
|||
|
||||
|
||||
@app.route('/mfa/disable', methods=['POST'])
|
||||
@authorized_personnel_only(admin = False)
|
||||
@authorized_personnel_only
|
||||
def totp_post_disable():
|
||||
# Anyone accessing this route is an admin, and we permit them to
|
||||
# disable the MFA status for any user if they submit a 'user' form
|
||||
# field.
|
||||
# user field if given, otherwise the user making the request
|
||||
email = request.form.get('user', request.user_email)
|
||||
if "admin" not in request.user_privs and email != request.user_email:
|
||||
return ("You are not an administrator; you can only view your own MFA status!", 403)
|
||||
|
||||
try:
|
||||
result = disable_mfa(email,
|
||||
request.form.get('mfa-id') or None,
|
||||
|
@ -658,14 +648,14 @@ def totp_post_disable():
|
|||
|
||||
|
||||
@app.route('/web/domains')
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def web_get_domains():
|
||||
from web_update import get_web_domains_info
|
||||
return json_response(get_web_domains_info(env))
|
||||
|
||||
|
||||
@app.route('/web/update', methods=['POST'])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def web_update():
|
||||
from web_update import do_web_update
|
||||
try:
|
||||
|
@ -678,7 +668,7 @@ def web_update():
|
|||
|
||||
|
||||
@app.route('/system/version', methods=["GET"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def system_version():
|
||||
from status_checks import what_version_is_this
|
||||
try:
|
||||
|
@ -688,7 +678,7 @@ def system_version():
|
|||
|
||||
|
||||
@app.route('/system/latest-upstream-version', methods=["POST"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def system_latest_upstream_version():
|
||||
from status_checks import get_latest_miab_version
|
||||
try:
|
||||
|
@ -698,7 +688,7 @@ def system_latest_upstream_version():
|
|||
|
||||
|
||||
@app.route('/system/status', methods=["POST"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def system_status():
|
||||
from status_checks import run_checks
|
||||
|
||||
|
@ -746,7 +736,7 @@ def system_status():
|
|||
|
||||
|
||||
@app.route('/system/updates')
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def show_updates():
|
||||
from status_checks import list_apt_updates
|
||||
return "".join("%s (%s)\n" % (p["package"], p["version"])
|
||||
|
@ -754,7 +744,7 @@ def show_updates():
|
|||
|
||||
|
||||
@app.route('/system/update-packages', methods=["POST"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def do_updates():
|
||||
utils.shell("check_call", ["/usr/bin/apt-get", "-qq", "update"])
|
||||
return utils.shell("check_output", ["/usr/bin/apt-get", "-y", "upgrade"],
|
||||
|
@ -762,7 +752,7 @@ def do_updates():
|
|||
|
||||
|
||||
@app.route('/system/reboot', methods=["GET"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def needs_reboot():
|
||||
from status_checks import is_reboot_needed_due_to_package_installation
|
||||
if is_reboot_needed_due_to_package_installation():
|
||||
|
@ -772,7 +762,7 @@ def needs_reboot():
|
|||
|
||||
|
||||
@app.route('/system/reboot', methods=["POST"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def do_reboot():
|
||||
# To keep the attack surface low, we don't allow a remote reboot if one isn't necessary.
|
||||
from status_checks import is_reboot_needed_due_to_package_installation
|
||||
|
@ -784,7 +774,7 @@ def do_reboot():
|
|||
|
||||
|
||||
@app.route('/system/backup/status')
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def backup_status():
|
||||
from backup import backup_status
|
||||
try:
|
||||
|
@ -794,14 +784,14 @@ def backup_status():
|
|||
|
||||
|
||||
@app.route('/system/backup/config', methods=["GET"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def backup_get_custom():
|
||||
from backup import get_backup_config
|
||||
return json_response(get_backup_config(env, for_ui=True))
|
||||
|
||||
|
||||
@app.route('/system/backup/config', methods=["POST"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def backup_set_custom():
|
||||
from backup import backup_set_custom
|
||||
return json_response(
|
||||
|
@ -813,7 +803,7 @@ def backup_set_custom():
|
|||
|
||||
|
||||
@app.route('/system/backup/new', methods=["POST"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def backup_new():
|
||||
from backup import perform_backup, get_backup_config
|
||||
|
||||
|
@ -827,14 +817,14 @@ def backup_new():
|
|||
|
||||
|
||||
@app.route('/system/privacy', methods=["GET"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def privacy_status_get():
|
||||
config = utils.load_settings(env)
|
||||
return json_response(config.get("privacy", True))
|
||||
|
||||
|
||||
@app.route('/system/privacy', methods=["POST"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def privacy_status_set():
|
||||
config = utils.load_settings(env)
|
||||
config["privacy"] = (request.form.get('value') == "private")
|
||||
|
@ -843,7 +833,7 @@ def privacy_status_set():
|
|||
|
||||
|
||||
@app.route('/system/smtp/relay', methods=["GET"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def smtp_relay_get():
|
||||
config = utils.load_settings(env)
|
||||
|
||||
|
@ -874,7 +864,7 @@ def smtp_relay_get():
|
|||
|
||||
|
||||
@app.route('/system/smtp/relay', methods=["POST"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def smtp_relay_set():
|
||||
from editconf import edit_conf
|
||||
from os import chmod
|
||||
|
@ -886,39 +876,30 @@ def smtp_relay_set():
|
|||
newconf = request.form
|
||||
|
||||
# Is DKIM configured?
|
||||
sel = newconf.get("dkim_selector", "")
|
||||
rr = newconf.get("dkim_rr", "")
|
||||
check_dkim = True
|
||||
sel = newconf.get("dkim_selector")
|
||||
if sel is None or sel.strip() == "":
|
||||
config["SMTP_RELAY_DKIM_SELECTOR"] = None
|
||||
# Check that the key RR doesn't exist either, otherwise we cannot be
|
||||
# sure that the user wants to remove it.
|
||||
if rr.strip() != "":
|
||||
return ("Cannot publish a DKIM key without a selector!\n\
|
||||
If you want to set up a relay without a DKIM record, both the selector and the key need to be empty.", 400)
|
||||
config["SMTP_RELAY_DKIM_RR"] = None
|
||||
check_dkim = False
|
||||
elif re.fullmatch(r"[a-z\d\._][a-z\d\._\-]*", sel.strip()) is None:
|
||||
return ("The DKIM selector is invalid!", 400)
|
||||
|
||||
if check_dkim:
|
||||
# DKIM selector looks good, try processing the RR
|
||||
if rr.strip() == "":
|
||||
return ("Cannot publish a selector with an empty key!\n\
|
||||
If you want to set up a relay without a DKIM record, both the selector and the key need to be empty.", 400)
|
||||
# DKIM selector looks good, try processing the RR
|
||||
rr = newconf.get("dkim_rr", "")
|
||||
if rr.strip() == "":
|
||||
return ("Cannot publish a selector with an empty key!", 400)
|
||||
|
||||
components = {}
|
||||
for r in re.split(r"[;\s]+", rr):
|
||||
sp = re.split(r"\=", r)
|
||||
if len(sp) != 2:
|
||||
return ("DKIM public key RR is malformed!", 400)
|
||||
components[sp[0]] = sp[1]
|
||||
components = {}
|
||||
for r in re.split(r"[;\s]+", rr):
|
||||
sp = re.split(r"\=", r)
|
||||
if len(sp) != 2:
|
||||
return ("DKIM public key RR is malformed!", 400)
|
||||
components[sp[0]] = sp[1]
|
||||
|
||||
if not components.get("p"):
|
||||
return ("The DKIM public key doesn't exist!", 400)
|
||||
if not components.get("p"):
|
||||
return ("The DKIM public key doesn't exist!", 400)
|
||||
|
||||
config["SMTP_RELAY_DKIM_SELECTOR"] = sel
|
||||
config["SMTP_RELAY_DKIM_RR"] = components
|
||||
config["SMTP_RELAY_DKIM_SELECTOR"] = sel
|
||||
config["SMTP_RELAY_DKIM_RR"] = components
|
||||
|
||||
relay_on = False
|
||||
implicit_tls = False
|
||||
|
@ -937,7 +918,7 @@ def smtp_relay_set():
|
|||
implicit_tls = True
|
||||
except ssl.SSLError as sle:
|
||||
# Couldn't connect via TLS, configure Postfix to send via STARTTLS
|
||||
pass
|
||||
print(sle.reason)
|
||||
except (socket.herror, socket.gaierror) as he:
|
||||
return (
|
||||
f"Unable to resolve hostname (it probably is incorrect): {he.strerror}",
|
||||
|
@ -1014,7 +995,7 @@ def smtp_relay_set():
|
|||
|
||||
|
||||
@app.route('/system/pgp/', methods=["GET"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def get_keys():
|
||||
from pgp import get_daemon_key, get_imported_keys, key_representation
|
||||
return {
|
||||
|
@ -1024,7 +1005,7 @@ def get_keys():
|
|||
|
||||
|
||||
@app.route('/system/pgp/<fpr>', methods=["GET"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def get_key(fpr):
|
||||
from pgp import get_key, key_representation
|
||||
k = get_key(fpr)
|
||||
|
@ -1034,7 +1015,7 @@ def get_key(fpr):
|
|||
|
||||
|
||||
@app.route('/system/pgp/<fpr>', methods=["DELETE"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def delete_key(fpr):
|
||||
from pgp import delete_key
|
||||
from wkd import parse_wkd_list, build_wkd
|
||||
|
@ -1049,7 +1030,7 @@ def delete_key(fpr):
|
|||
|
||||
|
||||
@app.route('/system/pgp/<fpr>/export', methods=["GET"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def export_key(fpr):
|
||||
from pgp import export_key
|
||||
exp = export_key(fpr)
|
||||
|
@ -1059,7 +1040,7 @@ def export_key(fpr):
|
|||
|
||||
|
||||
@app.route('/system/pgp/import', methods=["POST"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def import_key():
|
||||
from pgp import import_key
|
||||
from wkd import build_wkd
|
||||
|
@ -1084,7 +1065,7 @@ def import_key():
|
|||
|
||||
|
||||
@app.route('/system/pgp/wkd', methods=["GET"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def get_wkd_status():
|
||||
from pgp import get_daemon_key, get_imported_keys, key_representation
|
||||
from wkd import get_user_fpr_maps, get_wkd_config
|
||||
|
@ -1118,7 +1099,7 @@ def get_wkd_status():
|
|||
|
||||
|
||||
@app.route('/system/pgp/wkd', methods=["POST"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def update_wkd():
|
||||
from wkd import update_wkd_config, build_wkd
|
||||
update_wkd_config(request.form)
|
||||
|
@ -1127,7 +1108,7 @@ def update_wkd():
|
|||
|
||||
|
||||
@app.route('/system/default-quota', methods=["GET"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def default_quota_get():
|
||||
if request.values.get('text'):
|
||||
return get_default_quota(env)
|
||||
|
@ -1138,7 +1119,7 @@ def default_quota_get():
|
|||
|
||||
|
||||
@app.route('/system/default-quota', methods=["POST"])
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def default_quota_set():
|
||||
config = utils.load_settings(env)
|
||||
try:
|
||||
|
@ -1156,7 +1137,7 @@ def default_quota_set():
|
|||
|
||||
|
||||
@app.route('/munin/')
|
||||
@authorized_personnel_only()
|
||||
@authorized_personnel_only
|
||||
def munin_start():
|
||||
# Munin pages, static images, and dynamically generated images are served
|
||||
# outside of the AJAX API. We'll start with a 'start' API that sets a cookie
|
||||
|
|
|
@ -116,16 +116,7 @@ def do_dns_update(env, force=False):
|
|||
|
||||
# Tell nsd to reload changed zone files.
|
||||
if len(updated_domains) > 0:
|
||||
# 'reconfig' is needed if there are added or removed zones, but
|
||||
# it may not reload existing zones, so we call 'reload' too. If
|
||||
# nsd isn't running, nsd-control fails, so in that case revert
|
||||
# to restarting nsd to make sure it is running. Restarting nsd
|
||||
# should also refresh everything.
|
||||
try:
|
||||
shell('check_call', ["/usr/sbin/nsd-control", "reconfig"])
|
||||
shell('check_call', ["/usr/sbin/nsd-control", "reload"])
|
||||
except:
|
||||
shell('check_call', ["/usr/sbin/service", "nsd", "restart"])
|
||||
shell('check_call', ["/usr/sbin/nsd-control", "reload"])
|
||||
|
||||
# Write the OpenDKIM configuration tables for all of the mail domains.
|
||||
from mailconfig import get_mail_domains
|
||||
|
|
|
@ -1425,7 +1425,7 @@ def get_latest_miab_version():
|
|||
return re.search(
|
||||
b'TAG=(.*)',
|
||||
urlopen(
|
||||
"https://power-mailinabox.net/setup.sh",
|
||||
"https://raw.githubusercontent.com/ddavness/power-mailinabox/main/setup/bootstrap.sh",
|
||||
timeout=5).read()).group(1).decode("utf8")
|
||||
except (HTTPError, URLError, timeout):
|
||||
return None
|
||||
|
|
|
@ -135,13 +135,6 @@
|
|||
Monitoring</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item me-1 me-xl-4 dropdown if-logged-in-not-admin">
|
||||
<button class="btn dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Your Account</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#manage-password" onclick="return show_panel(this);">Manage Password</a></li>
|
||||
<li><a class="dropdown-item" href="#mfa" onclick="return show_panel(this);">Two-Factor Authentication</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item me-1 me-xl-4 btn if-logged-in-not-admin" type="button" href="#mail-guide"
|
||||
onclick="return show_panel(this);">
|
||||
Mail Guide
|
||||
|
@ -205,10 +198,6 @@
|
|||
{% include "wkd.html" %}
|
||||
</div>
|
||||
|
||||
<div id="panel_manage-password" class="admin_panel">
|
||||
{% include "manage-password.html" %}
|
||||
</div>
|
||||
|
||||
<div id="panel_mfa" class="admin_panel">
|
||||
{% include "mfa.html" %}
|
||||
</div>
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
<div>
|
||||
<h2>Manage Password</h2>
|
||||
<p>Here you can change your account password. The new password is then valid for both this panel and your email.</p>
|
||||
<p>If you have client emails configured, you'll then need to update the configuration with the new password. See the <a href="#mail-guide" onclick="return show_panel(this);">Mail Guide</a> for more information about this.</p>
|
||||
|
||||
<form class="form-horizontal" role="form" onsubmit="set_password_self(); return false;">
|
||||
<div class="col-lg-10 col-xl-8 mb-3">
|
||||
<div class="input-group">
|
||||
<label for="manage-password-new" class="input-group-text col-3">New Password</label>
|
||||
<input type="password" placeholder="password" class="form-control" id="manage-password-new">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-10 col-xl-8 mb-3">
|
||||
<div class="input-group">
|
||||
<label for="manage-password-confirm" class="input-group-text col-3">Confirm Password</label>
|
||||
<input type="password" placeholder="password" class="form-control" id="manage-password-confirm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<button id="manage-password-submit" type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
<small>After changing your password, you'll be logged out from the account and will need to log in again.</small>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function set_password_self() {
|
||||
if ($('#manage-password-new').val() !== $('#manage-password-confirm').val()) {
|
||||
show_modal_error("Set Password", 'Passwords do not match!');
|
||||
return;
|
||||
}
|
||||
|
||||
let password = $('#manage-password-new').val()
|
||||
|
||||
api(
|
||||
"/mail/users/password",
|
||||
"POST",
|
||||
{
|
||||
email: api_credentials.username,
|
||||
password: password
|
||||
},
|
||||
function (r) {
|
||||
// Responses are multiple lines of pre-formatted text.
|
||||
show_modal_error("Set Password", $("<pre/>").text(r), () => {
|
||||
do_logout()
|
||||
$('#manage-password-new').val("")
|
||||
$('#manage-password-confirm').val("")
|
||||
});
|
||||
},
|
||||
function (r) {
|
||||
show_modal_error("Set Password", r);
|
||||
}
|
||||
);
|
||||
}
|
||||
</script>
|
|
@ -78,7 +78,7 @@
|
|||
|
||||
<h3>DKIM Configuration</h3>
|
||||
<p>DKIM allows receivers to verify that the email was sent by the relay you configured (this is, somebody you
|
||||
trust). <b>If your relay provider does not provide you with this information, it's probably safe to skip this step.</b></p>
|
||||
trust). <b>Not doing so will have your email sent to spam.</b></p>
|
||||
|
||||
<div class="col-lg-6 col-md-8 col-12">
|
||||
<div class="input-group">
|
||||
|
|
|
@ -304,15 +304,6 @@
|
|||
$("#backup-target-type").val("s3");
|
||||
var hostpath = r.target.substring(5).split('/');
|
||||
var host = hostpath.shift();
|
||||
let s3_options = $("#backup-target-s3-host-select option").map(function() {return this.value}).get()
|
||||
$("#backup-target-s3-host-select").val("other")
|
||||
for (let h of s3_options) {
|
||||
console.log(h)
|
||||
if (h == host) {
|
||||
$("#backup-target-s3-host-select").val(host)
|
||||
break
|
||||
}
|
||||
}
|
||||
$("#backup-target-s3-host").val(host);
|
||||
$("#backup-target-s3-path").val(hostpath.join('/'));
|
||||
} else if (r.target.substring(0, 5) == "b2://") {
|
||||
|
@ -374,18 +365,18 @@
|
|||
}
|
||||
|
||||
function init_inputs(target_type) {
|
||||
function set_host(host, overwrite_other) {
|
||||
function set_host(host) {
|
||||
if (host !== 'other') {
|
||||
$("#backup-target-s3-host").val(host);
|
||||
} else if (overwrite_other) {
|
||||
} else {
|
||||
$("#backup-target-s3-host").val('');
|
||||
}
|
||||
}
|
||||
if (target_type == "s3") {
|
||||
$('#backup-target-s3-host-select').off('change').on('change', function () {
|
||||
set_host($('#backup-target-s3-host-select').val(), true);
|
||||
set_host($('#backup-target-s3-host-select').val());
|
||||
});
|
||||
set_host($('#backup-target-s3-host-select').val(), false);
|
||||
set_host($('#backup-target-s3-host-select').val());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ if [ -z "$TAG" ]; then
|
|||
[ "$(echo $OS | grep -o 'Ubuntu 20.04')" == "Ubuntu 20.04" ] ||
|
||||
[ "$(echo $OS | grep -o 'Ubuntu 22.04')" == "Ubuntu 22.04" ]
|
||||
then
|
||||
TAG=v60.5
|
||||
TAG=v60.2
|
||||
elif [ "$OS" == "Debian GNU/Linux 10 (buster)" ]; then
|
||||
echo "We are going to install the last version of Power Mail-in-a-Box supporting Debian 10 (buster)."
|
||||
echo "IF THIS IS A NEW INSTALLATION, STOP NOW, AND USE A SUPPORTED DISTRIBUTION INSTEAD (ONE OF THESE):"
|
||||
|
@ -86,7 +86,7 @@ if [ ! -d $HOME/mailinabox ]; then
|
|||
echo Downloading Mail-in-a-Box $TAG. . .
|
||||
git clone \
|
||||
-b $TAG --depth 1 \
|
||||
https://git.nibbletools.com/beenull/power-mailinabox \
|
||||
https://github.com/ddavness/power-mailinabox \
|
||||
$HOME/mailinabox \
|
||||
< /dev/null 2> /dev/null
|
||||
|
||||
|
|
|
@ -25,20 +25,10 @@ if [ ! -f $db_path ]; then
|
|||
echo "CREATE TABLE noreply (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT NOT NULL UNIQUE);" | sqlite3 $db_path
|
||||
echo "CREATE TABLE mfa (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, type TEXT NOT NULL, secret TEXT NOT NULL, mru_token TEXT, label TEXT, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE);" | sqlite3 $db_path;
|
||||
echo "CREATE TABLE auto_aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, source TEXT NOT NULL UNIQUE, destination TEXT NOT NULL, permitted_senders TEXT);" | sqlite3 $db_path;
|
||||
else
|
||||
sql=$(sqlite3 $db_path "SELECT sql FROM sqlite_master WHERE name = 'users'");
|
||||
if echo $sql | grep --invert-match quota; then
|
||||
echo "ALTER TABLE users ADD COLUMN quota TEXT NOT NULL DEFAULT '0';" | sqlite3 $db_path;
|
||||
fi
|
||||
elif sqlite3 $db_path ".schema users" | grep --invert-match quota; then
|
||||
echo "ALTER TABLE users ADD COLUMN quota TEXT NOT NULL DEFAULT '0';" | sqlite3 $db_path;
|
||||
fi
|
||||
|
||||
# Recover the database if it was hit by the Roundcube password changer "bug" (#85)
|
||||
# If the journal_mode is set to wal, postfix cannot read it and we wouldn't
|
||||
# be able to send or receive mail.
|
||||
#
|
||||
# This operation is idempotent so it's safe to run even in healthy databases, too.
|
||||
echo "PRAGMA journal_mode=delete;" | sqlite3 $db_path > /dev/null
|
||||
|
||||
# ### User Authentication
|
||||
|
||||
# Have Dovecot query our database, and not system users, for authentication.
|
||||
|
|
|
@ -81,13 +81,12 @@ mkdir -p $assets_dir
|
|||
|
||||
# jQuery CDN URL
|
||||
jquery_version=3.6.1
|
||||
jquery_url=https://code.jquery.com # Check this link for new versions
|
||||
jquery_url=https://code.jquery.com
|
||||
|
||||
# Get jQuery
|
||||
wget_verify $jquery_url/jquery-$jquery_version.min.js ea61688671d0c3044f2c5b2f2c4af0a6620ac6c2 $assets_dir/jquery.min.js
|
||||
|
||||
# Bootstrap CDN URL
|
||||
# See https://github.com/twbs/bootstrap/releases to check for new versions
|
||||
bootstrap_version=5.2.2
|
||||
bootstrap_url=https://github.com/twbs/bootstrap/releases/download/v$bootstrap_version/bootstrap-$bootstrap_version-dist.zip
|
||||
|
||||
|
@ -98,12 +97,11 @@ mv $assets_dir/bootstrap-$bootstrap_version-dist $assets_dir/bootstrap
|
|||
rm -f /tmp/bootstrap.zip
|
||||
|
||||
# FontAwesome CDN URL
|
||||
# See https://github.com/FortAwesome/Font-Awesome/releases to check for new versions
|
||||
fontawesome_version=6.2.1
|
||||
fontawesome_version=6.2.0
|
||||
fontawesome_url=https://github.com/FortAwesome/Font-Awesome/releases/download/$fontawesome_version/fontawesome-free-$fontawesome_version-web.zip
|
||||
|
||||
# Get FontAwesome
|
||||
wget_verify $fontawesome_url cd0f2bcc9653b56e3e2dd82d6598aa6bbca8d796 /tmp/fontawesome.zip
|
||||
wget_verify $fontawesome_url cd6250deeb38ab707240200c573d2357eaf732a0 /tmp/fontawesome.zip
|
||||
unzip -q /tmp/fontawesome.zip -d $assets_dir
|
||||
mv $assets_dir/fontawesome-free-$fontawesome_version-web $assets_dir/fontawesome
|
||||
rm -f /tmp/fontawesome.zip
|
||||
|
|
|
@ -21,8 +21,8 @@ echo "Installing Nextcloud (contacts/calendar)..."
|
|||
# we automatically install intermediate versions as needed.
|
||||
# * The hash is the SHA1 hash of the ZIP package, which you can find by just running this script and
|
||||
# copying it from the error message when it doesn't match what is below.
|
||||
nextcloud_ver=24.0.7
|
||||
nextcloud_hash=7fb1afeb3c212bf5530c3d234b23bf314b47655a
|
||||
nextcloud_ver=24.0.6
|
||||
nextcloud_hash=68366ddf16966acf532b3d0349a78ac2ade8269c
|
||||
|
||||
# Nextcloud apps
|
||||
# --------------
|
||||
|
@ -35,8 +35,8 @@ nextcloud_hash=7fb1afeb3c212bf5530c3d234b23bf314b47655a
|
|||
# copying it from the error message when it doesn't match what is below.
|
||||
contacts_ver=4.2.2
|
||||
contacts_hash=cbab9a7acdc11a9e2779c20b850bb21faec1c80f
|
||||
calendar_ver=3.5.2
|
||||
calendar_hash=dcf2cba6933dc8805ca4b4d04ed7b993ff4652a1
|
||||
calendar_ver=3.5.0
|
||||
calendar_hash=0938ffc4880cfdd74dd2e281eed96aa1f13fd065
|
||||
user_external_ver=3.0.0
|
||||
user_external_hash=0df781b261f55bbde73d8c92da3f99397000972f
|
||||
|
||||
|
@ -168,28 +168,10 @@ InstallNextcloud() {
|
|||
# $STORAGE_ROOT/owncloud is kept together even during a backup. It is better to rely on config.php than
|
||||
# version.php since the restore procedure can leave the system in a state where you have a newer Nextcloud
|
||||
# application version than the database.
|
||||
|
||||
# If config.php exists, get version number, otherwise CURRENT_NEXTCLOUD_VER is empty.
|
||||
#
|
||||
# Config unlocking, power-mailinabox#86
|
||||
# If a configuration file already exists, remove the "readonly" tag before starting the upgrade. This is
|
||||
# necessary (otherwise upgrades will fail).
|
||||
#
|
||||
# The lock will be re-applied further down the line when it's safe to do so.
|
||||
CONFIG_TEMP=$(/bin/mktemp)
|
||||
if [ -f "$STORAGE_ROOT/owncloud/config.php" ]; then
|
||||
CURRENT_NEXTCLOUD_VER=$(php -r "include(\"$STORAGE_ROOT/owncloud/config.php\"); echo(\$CONFIG['version']);")
|
||||
# Unlock configuration directory for upgrades
|
||||
php <<EOF > $CONFIG_TEMP && mv $CONFIG_TEMP $STORAGE_ROOT/owncloud/config.php;
|
||||
<?php
|
||||
include("$STORAGE_ROOT/owncloud/config.php");
|
||||
|
||||
\$CONFIG['config_is_read_only'] = false;
|
||||
|
||||
echo "<?php\n\\\$CONFIG = ";
|
||||
var_export(\$CONFIG);
|
||||
echo ";";
|
||||
?>
|
||||
EOF
|
||||
else
|
||||
CURRENT_NEXTCLOUD_VER=""
|
||||
fi
|
||||
|
@ -364,6 +346,7 @@ fi
|
|||
# the correct domain name if the domain is being change from the previous setup.
|
||||
# Use PHP to read the settings file, modify it, and write out the new settings array.
|
||||
TIMEZONE=$(cat /etc/timezone)
|
||||
CONFIG_TEMP=$(/bin/mktemp)
|
||||
php <<EOF > $CONFIG_TEMP && mv $CONFIG_TEMP $STORAGE_ROOT/owncloud/config.php;
|
||||
<?php
|
||||
include("$STORAGE_ROOT/owncloud/config.php");
|
||||
|
|
|
@ -49,12 +49,12 @@ fi
|
|||
|
||||
# Put a start script in a global location. We tell the user to run 'mailinabox'
|
||||
# in the first dialog prompt, so we should do this before that starts.
|
||||
cat > /usr/local/sbin/mailinabox << EOF;
|
||||
cat > /usr/local/bin/mailinabox << EOF;
|
||||
#!/bin/bash
|
||||
cd $(pwd)
|
||||
source setup/start.sh
|
||||
EOF
|
||||
chmod 744 /usr/local/sbin/mailinabox
|
||||
chmod +x /usr/local/bin/mailinabox
|
||||
|
||||
# Ask the user for the PRIMARY_HOSTNAME, PUBLIC_IP, and PUBLIC_IPV6,
|
||||
# if values have not already been set in environment variables. When running
|
||||
|
@ -129,14 +129,6 @@ source setup/zpush.sh
|
|||
source setup/management.sh
|
||||
source setup/munin.sh
|
||||
|
||||
# Create a shorthand alias for the cli interface
|
||||
cat > /usr/local/sbin/miabadm << EOF;
|
||||
#!/bin/bash
|
||||
cd $(pwd)
|
||||
/usr/bin/env python3 management/cli.py \$@
|
||||
EOF
|
||||
chmod 744 /usr/local/sbin/miabadm
|
||||
|
||||
# Wait for the management daemon to start...
|
||||
until nc -z -w 4 127.0.0.1 10222
|
||||
do
|
||||
|
|
|
@ -20,7 +20,7 @@ hostname $PRIMARY_HOSTNAME
|
|||
# the loopback interface to also work on IPv6 (that is, we want :: to be available). This
|
||||
# is required because apparently nsd expects this to exist.
|
||||
|
||||
management/editconf.py /etc/sysctl.conf "net.ipv6.conf.lo.disable_ipv6 = 0"
|
||||
management/editconf.py /etc/sysctl.conf "net.ipv6.conf.all.disable_ipv6 = 0"
|
||||
hide_output sysctl --system
|
||||
|
||||
# ### Fix permissions
|
||||
|
|
|
@ -30,8 +30,8 @@ apt_install \
|
|||
# whether we have the latest version of everything.
|
||||
# For the latest versions, see:
|
||||
# https://github.com/roundcube/roundcubemail/releases
|
||||
# https://github.com/mfreiholz/persistent_login/
|
||||
# https://github.com/stremlau/html5_notifier/
|
||||
# https://github.com/mfreiholz/persistent_login/commits/master
|
||||
# https://github.com/stremlau/html5_notifier/commits/master
|
||||
# https://github.com/mstilkerich/rcmcarddav/releases
|
||||
# The easiest way to get the package hashes is to run this script and get the hash from
|
||||
# the error message.
|
||||
|
@ -39,8 +39,8 @@ VERSION=1.6.0
|
|||
HASH=fd84b4fac74419bb73e7a3bcae1978d5589c52de
|
||||
PERSISTENT_LOGIN_VERSION=version-5.3.0
|
||||
HTML5_NOTIFIER_VERSION=68d9ca194212e15b3c7225eb6085dbcf02fd13d7 # version 0.6.4+
|
||||
CARDDAV_VERSION=4.4.4
|
||||
CARDDAV_HASH=743fd6925b775f821aa8860982d2bdeec05f5d7b
|
||||
CARDDAV_VERSION=4.4.3
|
||||
CARDDAV_HASH=74f8ba7aee33e78beb9de07f7f44b81f6071b644
|
||||
|
||||
UPDATE_KEY=$VERSION:$PERSISTENT_LOGIN_VERSION:$HTML5_NOTIFIER_VERSION:$CARDDAV_VERSION
|
||||
|
||||
|
@ -212,11 +212,12 @@ cp ${RCM_PLUGIN_DIR}/password/config.inc.php.dist \
|
|||
${RCM_PLUGIN_DIR}/password/config.inc.php
|
||||
|
||||
management/editconf.py ${RCM_PLUGIN_DIR}/password/config.inc.php -c "//" \
|
||||
"\$config['password_driver'] = 'miab';" \
|
||||
"\$config['password_minimum_length'] = 8;" \
|
||||
"\$config['password_miab_url'] = 'http://127.0.0.1:10222/';" \
|
||||
"\$config['password_miab_user'] = '';" \
|
||||
"\$config['password_miab_pass'] = '';"
|
||||
"\$config['password_db_dsn'] = 'sqlite:///$STORAGE_ROOT/mail/users.sqlite';" \
|
||||
"\$config['password_query'] = 'UPDATE users SET password=%P WHERE email=%u';" \
|
||||
"\$config['password_algorithm'] = 'sha512-crypt';" \
|
||||
"\$config['password_algorithm_prefix'] = '{SHA512-CRYPT}';" \
|
||||
"\$config['password_dovecotpw_with_method'] = false;"
|
||||
|
||||
# so PHP can use doveadm, for the password changing plugin
|
||||
usermod -a -G dovecot www-data
|
||||
|
|
Loading…
Add table
Reference in a new issue