mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2026-03-18 18:07:22 +01:00
Compare commits
18 Commits
v68
...
f62bdc33d8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f62bdc33d8 | ||
|
|
f82be1b43f | ||
|
|
1795f8aefd | ||
|
|
a332be6a7b | ||
|
|
c7faccf1fa | ||
|
|
ec497efa69 | ||
|
|
55a8be4aa9 | ||
|
|
3399b25084 | ||
|
|
2afd0451c1 | ||
|
|
27cf11d8ec | ||
|
|
44d9f6eebd | ||
|
|
4b7d4ba0a6 | ||
|
|
67bcaea71e | ||
|
|
bdf4155bed | ||
|
|
f1888f2043 | ||
|
|
33559bb844 | ||
|
|
30c4681e80 | ||
|
|
133bae1300 |
63
conf/dovecot/conf.d/15-mailboxes.conf
Normal file
63
conf/dovecot/conf.d/15-mailboxes.conf
Normal file
@@ -0,0 +1,63 @@
|
||||
## NOTE: This file is automatically generated by Mail-in-a-Box.
|
||||
## Do not edit this file. It is continually updated by
|
||||
## Mail-in-a-Box and your changes will be lost.
|
||||
##
|
||||
## Mail-in-a-Box machines are not meant to be modified.
|
||||
## If you modify any system configuration you are on
|
||||
## your own --- please do not ask for help from us.
|
||||
|
||||
namespace inbox {
|
||||
# Automatically create & subscribe some folders.
|
||||
# * Create and subscribe the INBOX folder.
|
||||
# * Our sieve rule for spam expects that the Spam folder exists.
|
||||
# * Z-Push must be configured with the same settings in conf/zpush/backend_imap.php (#580).
|
||||
|
||||
# MUA notes:
|
||||
# * Roundcube will show an error if the user tries to delete a message before the Trash folder exists (#359).
|
||||
# * K-9 mail will poll every 90 seconds if a Drafts folder does not exist.
|
||||
# * Apple's OS X Mail app will create 'Sent Messages' if it doesn't see a folder with the \Sent flag (#571, #573) and won't be able to archive messages unless 'Archive' exists (#581).
|
||||
# * Thunderbird's default in its UI is 'Archives' (plural) but it will configure new accounts to use whatever we say here (#581).
|
||||
|
||||
# auto:
|
||||
# 'create' will automatically create this mailbox.
|
||||
# 'subscribe' will both create and subscribe to the mailbox.
|
||||
|
||||
# special_use is a space separated list of IMAP SPECIAL-USE
|
||||
# attributes as specified by RFC 6154:
|
||||
# \All \Archive \Drafts \Flagged \Junk \Sent \Trash
|
||||
|
||||
mailbox INBOX {
|
||||
auto = subscribe
|
||||
}
|
||||
mailbox Spam {
|
||||
special_use = \Junk
|
||||
auto = subscribe
|
||||
}
|
||||
mailbox Drafts {
|
||||
special_use = \Drafts
|
||||
auto = subscribe
|
||||
}
|
||||
mailbox Sent {
|
||||
special_use = \Sent
|
||||
auto = subscribe
|
||||
}
|
||||
mailbox Trash {
|
||||
special_use = \Trash
|
||||
auto = subscribe
|
||||
}
|
||||
mailbox Archive {
|
||||
special_use = \Archive
|
||||
auto = subscribe
|
||||
}
|
||||
|
||||
# dovevot's standard mailboxes configuration file marks two sent folders
|
||||
# with the \Sent attribute, just in case clients don't agree about which
|
||||
# they're using. We'll keep that, plus add Junk as an alterative for Spam.
|
||||
# These are not auto-created.
|
||||
mailbox "Sent Messages" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox Junk {
|
||||
special_use = \Junk
|
||||
}
|
||||
}
|
||||
@@ -60,11 +60,13 @@ def setup_key_auth(mgmt_uri):
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("""Usage:
|
||||
{cli} system default-quota [new default] (set default quota for system)
|
||||
{cli} user (lists users)
|
||||
{cli} user add user@domain.com [password]
|
||||
{cli} user password user@domain.com [password]
|
||||
{cli} user remove user@domain.com
|
||||
{cli} user make-admin user@domain.com
|
||||
{cli} user quota user@domain [new-quota]
|
||||
{cli} user remove-admin user@domain.com
|
||||
{cli} user admins (lists admins)
|
||||
{cli} user mfa show user@domain.com (shows MFA devices for user, if any)
|
||||
@@ -88,6 +90,10 @@ elif sys.argv[1] == "user" and len(sys.argv) == 2:
|
||||
print(user['email'], end='')
|
||||
if "admin" in user['privileges']:
|
||||
print("*", end='')
|
||||
if user['quota'] == '0':
|
||||
print(" unlimited", end='')
|
||||
else:
|
||||
print(" " + user['quota'], end='')
|
||||
print()
|
||||
|
||||
elif sys.argv[1] == "user" and sys.argv[2] in {"add", "password"}:
|
||||
@@ -117,6 +123,14 @@ elif sys.argv[1] == "user" and sys.argv[2] == "admins":
|
||||
if "admin" in user['privileges']:
|
||||
print(user['email'])
|
||||
|
||||
elif sys.argv[1] == "user" and sys.argv[2] == "quota" and len(sys.argv) == 4:
|
||||
# Set a user's quota
|
||||
print(mgmt("/mail/users/quota?text=1&email=%s" % sys.argv[3]))
|
||||
|
||||
elif sys.argv[1] == "user" and sys.argv[2] == "quota" and len(sys.argv) == 5:
|
||||
# Set a user's quota
|
||||
users = mgmt("/mail/users/quota", { "email": sys.argv[3], "quota": sys.argv[4] })
|
||||
|
||||
elif sys.argv[1] == "user" and len(sys.argv) == 5 and sys.argv[2:4] == ["mfa", "show"]:
|
||||
# Show MFA status for a user.
|
||||
status = mgmt("/mfa/status", { "user": sys.argv[4] }, is_json=True)
|
||||
@@ -138,6 +152,12 @@ elif sys.argv[1] == "alias" and sys.argv[2] == "add" and len(sys.argv) == 5:
|
||||
elif sys.argv[1] == "alias" and sys.argv[2] == "remove" and len(sys.argv) == 4:
|
||||
print(mgmt("/mail/aliases/remove", { "address": sys.argv[3] }))
|
||||
|
||||
elif sys.argv[1] == "system" and sys.argv[2] == "default-quota" and len(sys.argv) == 3:
|
||||
print(mgmt("/system/default-quota?text=1"))
|
||||
|
||||
elif sys.argv[1] == "system" and sys.argv[2] == "default-quota" and len(sys.argv) == 4:
|
||||
print(mgmt("/system/default-quota", { "default_quota": sys.argv[3]}))
|
||||
|
||||
else:
|
||||
print("Invalid command-line arguments.")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -21,6 +21,7 @@ import auth, utils
|
||||
from mailconfig import get_mail_users, get_mail_users_ex, get_admins, add_mail_user, set_mail_password, remove_mail_user
|
||||
from mailconfig import get_mail_user_privileges, add_remove_mail_user_privilege
|
||||
from mailconfig import get_mail_aliases, get_mail_aliases_ex, get_mail_domains, add_mail_alias, remove_mail_alias
|
||||
from mailconfig import get_mail_quota, set_mail_quota, get_default_quota, validate_quota
|
||||
from mfa import get_public_mfa_state, provision_totp, validate_totp_secret, enable_mfa, disable_mfa
|
||||
import contextlib
|
||||
|
||||
@@ -191,8 +192,31 @@ def mail_users():
|
||||
@app.route('/mail/users/add', methods=['POST'])
|
||||
@authorized_personnel_only
|
||||
def mail_users_add():
|
||||
quota = request.form.get('quota', get_default_quota(env))
|
||||
try:
|
||||
return add_mail_user(request.form.get('email', ''), request.form.get('password', ''), request.form.get('privileges', ''), env)
|
||||
return add_mail_user(request.form.get('email', ''), request.form.get('password', ''), request.form.get('privileges', ''), quota, env)
|
||||
except ValueError as e:
|
||||
return (str(e), 400)
|
||||
|
||||
@app.route('/mail/users/quota', methods=['GET'])
|
||||
@authorized_personnel_only
|
||||
def get_mail_users_quota():
|
||||
email = request.values.get('email', '')
|
||||
quota = get_mail_quota(email, env)
|
||||
|
||||
if request.values.get('text'):
|
||||
return quota
|
||||
|
||||
return json_response({
|
||||
"email": email,
|
||||
"quota": quota
|
||||
})
|
||||
|
||||
@app.route('/mail/users/quota', methods=['POST'])
|
||||
@authorized_personnel_only
|
||||
def mail_users_quota():
|
||||
try:
|
||||
return set_mail_quota(request.form.get('email', ''), request.form.get('quota'), env)
|
||||
except ValueError as e:
|
||||
return (str(e), 400)
|
||||
|
||||
@@ -651,6 +675,29 @@ def privacy_status_set():
|
||||
utils.write_settings(config, env)
|
||||
return "OK"
|
||||
|
||||
@app.route('/system/default-quota', methods=["GET"])
|
||||
@authorized_personnel_only
|
||||
def default_quota_get():
|
||||
if request.values.get('text'):
|
||||
return get_default_quota(env)
|
||||
else:
|
||||
return json_response({
|
||||
"default-quota": get_default_quota(env),
|
||||
})
|
||||
|
||||
@app.route('/system/default-quota', methods=["POST"])
|
||||
@authorized_personnel_only
|
||||
def default_quota_set():
|
||||
config = utils.load_settings(env)
|
||||
try:
|
||||
config["default-quota"] = validate_quota(request.values.get('default_quota'))
|
||||
utils.write_settings(config, env)
|
||||
|
||||
except ValueError as e:
|
||||
return ("ERROR: %s" % str(e), 400)
|
||||
|
||||
return "OK"
|
||||
|
||||
# MUNIN
|
||||
|
||||
@app.route('/munin/')
|
||||
|
||||
@@ -11,7 +11,7 @@ export LC_TYPE=en_US.UTF-8
|
||||
|
||||
# On Mondays, i.e. once a week, send the administrator a report of total emails
|
||||
# sent and received so the admin might notice server abuse.
|
||||
if [ `date "+%u"` -eq 1 ]; then
|
||||
if [ "$(date "+%u")" -eq 1 ]; then
|
||||
management/mail_log.py -t week | management/email_administrator.py "Mail-in-a-Box Usage Report"
|
||||
fi
|
||||
|
||||
|
||||
@@ -102,6 +102,18 @@ def get_mail_users(env):
|
||||
users = [ row[0] for row in c.fetchall() ]
|
||||
return utils.sort_email_addresses(users, env)
|
||||
|
||||
def sizeof_fmt(num):
|
||||
for unit in ['','K','M','G','T']:
|
||||
if abs(num) < 1024.0:
|
||||
if abs(num) > 99:
|
||||
return "%3.0f%s" % (num, unit)
|
||||
else:
|
||||
return "%2.1f%s" % (num, unit)
|
||||
|
||||
num /= 1024.0
|
||||
|
||||
return str(num)
|
||||
|
||||
def get_mail_users_ex(env, with_archived=False):
|
||||
# Returns a complex data structure of all user accounts, optionally
|
||||
# including archived (status="inactive") accounts.
|
||||
@@ -125,13 +137,46 @@ def get_mail_users_ex(env, with_archived=False):
|
||||
users = []
|
||||
active_accounts = set()
|
||||
c = open_database(env)
|
||||
c.execute('SELECT email, privileges FROM users')
|
||||
for email, privileges in c.fetchall():
|
||||
c.execute('SELECT email, privileges, quota FROM users')
|
||||
for email, privileges, quota in c.fetchall():
|
||||
active_accounts.add(email)
|
||||
|
||||
(user, domain) = email.split('@')
|
||||
box_size = 0
|
||||
box_count = 0
|
||||
box_quota = 0
|
||||
percent = ''
|
||||
try:
|
||||
dirsize_file = os.path.join(env['STORAGE_ROOT'], 'mail/mailboxes/%s/%s/maildirsize' % (domain, user))
|
||||
with open(dirsize_file, 'r') as f:
|
||||
box_quota = int(f.readline().split('S')[0])
|
||||
for line in f.readlines():
|
||||
(size, count) = line.split(' ')
|
||||
box_size += int(size)
|
||||
box_count += int(count)
|
||||
|
||||
try:
|
||||
percent = (box_size / box_quota) * 100
|
||||
except:
|
||||
percent = 'Error'
|
||||
|
||||
except:
|
||||
box_size = '?'
|
||||
box_count = '?'
|
||||
box_quota = '?'
|
||||
percent = '?'
|
||||
|
||||
if quota == '0':
|
||||
percent = ''
|
||||
|
||||
user = {
|
||||
"email": email,
|
||||
"privileges": parse_privs(privileges),
|
||||
"quota": quota,
|
||||
"box_quota": box_quota,
|
||||
"box_size": sizeof_fmt(box_size) if box_size != '?' else box_size,
|
||||
"percent": '%3.0f%%' % percent if type(percent) != str else percent,
|
||||
"box_count": box_count,
|
||||
"status": "active",
|
||||
}
|
||||
users.append(user)
|
||||
@@ -150,6 +195,10 @@ def get_mail_users_ex(env, with_archived=False):
|
||||
"privileges": [],
|
||||
"status": "inactive",
|
||||
"mailbox": mbox,
|
||||
"box_count": '?',
|
||||
"box_size": '?',
|
||||
"box_quota": '?',
|
||||
"percent": '?',
|
||||
}
|
||||
users.append(user)
|
||||
|
||||
@@ -266,7 +315,7 @@ def get_mail_domains(env, filter_aliases=lambda alias : True, users_only=False):
|
||||
domains.extend([get_domain(address, as_unicode=False) for address, _, _, auto in get_mail_aliases(env) if filter_aliases(address) and not auto ])
|
||||
return set(domains)
|
||||
|
||||
def add_mail_user(email, pw, privs, env):
|
||||
def add_mail_user(email, pw, privs, quota, env):
|
||||
# validate email
|
||||
if email.strip() == "":
|
||||
return ("No email address provided.", 400)
|
||||
@@ -292,6 +341,14 @@ def add_mail_user(email, pw, privs, env):
|
||||
validation = validate_privilege(p)
|
||||
if validation: return validation
|
||||
|
||||
if quota is None:
|
||||
quota = get_default_quota()
|
||||
|
||||
try:
|
||||
quota = validate_quota(quota)
|
||||
except ValueError as e:
|
||||
return (str(e), 400)
|
||||
|
||||
# get the database
|
||||
conn, c = open_database(env, with_connection=True)
|
||||
|
||||
@@ -300,14 +357,16 @@ def add_mail_user(email, pw, privs, env):
|
||||
|
||||
# add the user to the database
|
||||
try:
|
||||
c.execute("INSERT INTO users (email, password, privileges) VALUES (?, ?, ?)",
|
||||
(email, pw, "\n".join(privs)))
|
||||
c.execute("INSERT INTO users (email, password, privileges, quota) VALUES (?, ?, ?, ?)",
|
||||
(email, pw, "\n".join(privs), quota))
|
||||
except sqlite3.IntegrityError:
|
||||
return ("User already exists.", 400)
|
||||
|
||||
# write databasebefore next step
|
||||
conn.commit()
|
||||
|
||||
dovecot_quota_recalc(email)
|
||||
|
||||
# Update things in case any new domains are added.
|
||||
return kick(env, "mail user added")
|
||||
|
||||
@@ -332,6 +391,59 @@ def hash_password(pw):
|
||||
# http://wiki2.dovecot.org/Authentication/PasswordSchemes
|
||||
return utils.shell('check_output', ["/usr/bin/doveadm", "pw", "-s", "SHA512-CRYPT", "-p", pw]).strip()
|
||||
|
||||
|
||||
def get_mail_quota(email, env):
|
||||
conn, c = open_database(env, with_connection=True)
|
||||
c.execute("SELECT quota FROM users WHERE email=?", (email,))
|
||||
rows = c.fetchall()
|
||||
if len(rows) != 1:
|
||||
return ("That's not a user (%s)." % email, 400)
|
||||
|
||||
return rows[0][0]
|
||||
|
||||
|
||||
def set_mail_quota(email, quota, env):
|
||||
# validate that password is acceptable
|
||||
quota = validate_quota(quota)
|
||||
|
||||
# update the database
|
||||
conn, c = open_database(env, with_connection=True)
|
||||
c.execute("UPDATE users SET quota=? WHERE email=?", (quota, email))
|
||||
if c.rowcount != 1:
|
||||
return ("That's not a user (%s)." % email, 400)
|
||||
conn.commit()
|
||||
|
||||
dovecot_quota_recalc(email)
|
||||
|
||||
return "OK"
|
||||
|
||||
def dovecot_quota_recalc(email):
|
||||
# dovecot processes running for the user will not recognize the new quota setting
|
||||
# a reload is necessary to reread the quota setting, but it will also shut down
|
||||
# running dovecot processes. Email clients generally log back in when they lose
|
||||
# a connection.
|
||||
# subprocess.call(['doveadm', 'reload'])
|
||||
|
||||
# force dovecot to recalculate the quota info for the user.
|
||||
subprocess.call(["doveadm", "quota", "recalc", "-u", email])
|
||||
|
||||
def get_default_quota(env):
|
||||
config = utils.load_settings(env)
|
||||
return config.get("default-quota", '0')
|
||||
|
||||
def validate_quota(quota):
|
||||
# validate quota
|
||||
quota = quota.strip().upper()
|
||||
|
||||
if quota == "":
|
||||
raise ValueError("No quota provided.")
|
||||
if re.search(r"[\s,.]", quota):
|
||||
raise ValueError("Quotas cannot contain spaces, commas, or decimal points.")
|
||||
if not re.match(r'^[\d]+[GM]?$', quota):
|
||||
raise ValueError("Invalid quota.")
|
||||
|
||||
return quota
|
||||
|
||||
def get_mail_password(email, env):
|
||||
# Gets the hashed password for a user. Passwords are stored in Dovecot's
|
||||
# password format, with a prefixed scheme.
|
||||
|
||||
@@ -282,7 +282,7 @@ def run_network_checks(env, output):
|
||||
# The user might have ended up on an IP address that was previously in use
|
||||
# by a spammer, or the user may be deploying on a residential network. We
|
||||
# will not be able to reliably send mail in these cases.
|
||||
|
||||
|
||||
# See https://www.spamhaus.org/news/article/807/using-our-public-mirrors-check-your-return-codes-now. for
|
||||
# information on spamhaus return codes
|
||||
rev_ip4 = ".".join(reversed(env['PUBLIC_IP'].split('.')))
|
||||
@@ -721,7 +721,7 @@ def check_mail_domain(domain, env, output):
|
||||
# Stop if the domain is listed in the Spamhaus Domain Block List.
|
||||
# The user might have chosen a domain that was previously in use by a spammer
|
||||
# and will not be able to reliably send mail.
|
||||
|
||||
|
||||
# See https://www.spamhaus.org/news/article/807/using-our-public-mirrors-check-your-return-codes-now. for
|
||||
# information on spamhaus return codes
|
||||
dbl = query_dns(domain+'.dbl.spamhaus.org', "A", nxdomain=None)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#user_table .account_inactive .if_active { display: none; }
|
||||
#user_table .account_active .if_inactive { display: none; }
|
||||
#user_table .account_active.if_inactive { display: none; }
|
||||
.row-center { text-align: center; }
|
||||
</style>
|
||||
|
||||
<h3>Add a mail user</h3>
|
||||
@@ -27,6 +28,10 @@
|
||||
<option value="admin">Administrator</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="adduserQuota">Quota</label>
|
||||
<input type="text" class="form-control" id="adduserQuota" placeholder="Quota" style="width:5em;">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Add User</button>
|
||||
</form>
|
||||
<ul style="margin-top: 1em; padding-left: 1.5em; font-size: 90%;">
|
||||
@@ -34,13 +39,18 @@
|
||||
<li>Use <a href="#aliases">aliases</a> to create email addresses that forward to existing accounts.</li>
|
||||
<li>Administrators get access to this control panel.</li>
|
||||
<li>User accounts cannot contain any international (non-ASCII) characters, but <a href="#aliases">aliases</a> can.</li>
|
||||
<li>Quotas may not contain any spaces, commas or decimal points. Suffixes of G (gigabytes) and M (megabytes) are allowed. For unlimited storage enter 0 (zero)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Existing mail users</h3>
|
||||
<table id="user_table" class="table" style="width: auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="50%">Email Address</th>
|
||||
<th width="35%">Email Address</th>
|
||||
<th class="row-center">Messages</th>
|
||||
<th class="row-center">Size</th>
|
||||
<th class="row-center">Used</th>
|
||||
<th class="row-center">Quota</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -53,10 +63,22 @@
|
||||
<tr id="user-template">
|
||||
<td class='address'>
|
||||
</td>
|
||||
<td class="box-count row-center"></td>
|
||||
<td class="box-size row-center"></td>
|
||||
<td class="percent row-center"></td>
|
||||
<td class="quota row-center">
|
||||
</td>
|
||||
<td class='actions'>
|
||||
<span class='privs'>
|
||||
</span>
|
||||
|
||||
<span class="if_active">
|
||||
<a href="#" onclick="users_set_quota(this); return false;" class='setquota' title="Set Quota">
|
||||
set quota
|
||||
</a>
|
||||
|
|
||||
</span>
|
||||
|
||||
<span class="if_active">
|
||||
<a href="#" onclick="users_set_password(this); return false;" class='setpw' title="Set Password">
|
||||
set password
|
||||
@@ -97,10 +119,28 @@
|
||||
<table class="table" style="margin-top: .5em">
|
||||
<thead><th>Verb</th> <th>Action</th><th></th></thead>
|
||||
<tr><td>GET</td><td><i>(none)</i></td> <td>Returns a list of existing mail users. Adding <code>?format=json</code> to the URL will give JSON-encoded results.</td></tr>
|
||||
<tr><td>POST</td><td>/add</td> <td>Adds a new mail user. Required POST-body parameters are <code>email</code> and <code>password</code>.</td></tr>
|
||||
<tr><td>POST</td><td>/remove</td> <td>Removes a mail user. Required POST-body parameter is <code>email</code>.</td></tr>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td>/add</td>
|
||||
<td>Adds a new mail user. Required POST-body parameters are <code>email</code> and <code>password</code>. Optional parameters: <code>privilege=admin</code> and <code>quota</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td>/remove</td>
|
||||
<td>Removes a mail user. Required POST-by parameter is <code>email</code>.</td>
|
||||
</tr>
|
||||
<tr><td>POST</td><td>/privileges/add</td> <td>Used to make a mail user an admin. Required POST-body parameters are <code>email</code> and <code>privilege=admin</code>.</td></tr>
|
||||
<tr><td>POST</td><td>/privileges/remove</td> <td>Used to remove the admin privilege from a mail user. Required POST-body parameter is <code>email</code>.</td></tr>
|
||||
<tr>
|
||||
<td>GET</td>
|
||||
<td>/quota</td>
|
||||
<td>Get the quota for a mail user. Required POST-body parameters are <code>email</code> and will return JSON result</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td>/quota</td>
|
||||
<td>Set the quota for a mail user. Required POST-body parameters are <code>email</code> and <code>quota</code>.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h4>Examples:</h4>
|
||||
@@ -125,6 +165,15 @@ curl -X POST -d "email=new_user@mydomail.com" https://{{hostname}}/admin/mail/us
|
||||
|
||||
<script>
|
||||
function show_users() {
|
||||
api(
|
||||
"/system/default-quota",
|
||||
"GET",
|
||||
{},
|
||||
function(r) {
|
||||
$('#adduserQuota').val(r['default-quota']);
|
||||
}
|
||||
);
|
||||
|
||||
$('#user_table tbody').html("<tr><td colspan='2' class='text-muted'>Loading...</td></tr>")
|
||||
api(
|
||||
"/mail/users",
|
||||
@@ -133,7 +182,7 @@ function show_users() {
|
||||
function(r) {
|
||||
$('#user_table tbody').html("");
|
||||
for (var i = 0; i < r.length; i++) {
|
||||
var hdr = $("<tr><th colspan='2' style='background-color: #EEE'></th></tr>");
|
||||
var hdr = $("<tr><th colspan='6' style='background-color: #EEE'></th></tr>");
|
||||
hdr.find('th').text(r[i].domain);
|
||||
$('#user_table tbody').append(hdr);
|
||||
|
||||
@@ -151,7 +200,18 @@ function show_users() {
|
||||
n2.addClass("account_" + user.status);
|
||||
|
||||
n.attr('data-email', user.email);
|
||||
n.find('.address').text(user.email)
|
||||
n.attr('data-quota', user.quota);
|
||||
n.find('.address').text(user.email);
|
||||
n.find('.box-count').text((user.box_count).toLocaleString('en'));
|
||||
if (user.box_count == '?') {
|
||||
n.find('.box-count').attr('title', 'Message count is unkown')
|
||||
}
|
||||
n.find('.box-size').text(user.box_size);
|
||||
if (user.box_size == '?') {
|
||||
n.find('.box-size').attr('title', 'Mailbox size is unkown')
|
||||
}
|
||||
n.find('.percent').text(user.percent);
|
||||
n.find('.quota').text((user.quota == '0') ? 'unlimited' : user.quota);
|
||||
n2.find('.restore_info tt').text(user.mailbox);
|
||||
|
||||
if (user.status == 'inactive') continue;
|
||||
@@ -180,13 +240,15 @@ function do_add_user() {
|
||||
var email = $("#adduserEmail").val();
|
||||
var pw = $("#adduserPassword").val();
|
||||
var privs = $("#adduserPrivs").val();
|
||||
var quota = $("#adduserQuota").val();
|
||||
api(
|
||||
"/mail/users/add",
|
||||
"POST",
|
||||
{
|
||||
email: email,
|
||||
password: pw,
|
||||
privileges: privs
|
||||
privileges: privs,
|
||||
quota: quota
|
||||
},
|
||||
function(r) {
|
||||
// Responses are multiple lines of pre-formatted text.
|
||||
@@ -228,6 +290,36 @@ function users_set_password(elem) {
|
||||
});
|
||||
}
|
||||
|
||||
function users_set_quota(elem) {
|
||||
var email = $(elem).parents('tr').attr('data-email');
|
||||
var quota = $(elem).parents('tr').attr('data-quota');
|
||||
|
||||
show_modal_confirm(
|
||||
"Set Quota",
|
||||
$("<p>Set quota for <b>" + email + "</b>?</p>" +
|
||||
"<p>" +
|
||||
"<label for='users_set_quota' style='display: block; font-weight: normal'>Quota:</label>" +
|
||||
"<input type='text' id='users_set_quota' value='" + quota + "'></p>" +
|
||||
"<p><small>Quotas may not contain any spaces or commas. Suffixes of G (gigabytes) and M (megabytes) are allowed.</small></p>" +
|
||||
"<p><small>For unlimited storage enter 0 (zero)</small></p>"),
|
||||
"Set Quota",
|
||||
function() {
|
||||
api(
|
||||
"/mail/users/quota",
|
||||
"POST",
|
||||
{
|
||||
email: email,
|
||||
quota: $('#users_set_quota').val()
|
||||
},
|
||||
function(r) {
|
||||
show_users();
|
||||
},
|
||||
function(r) {
|
||||
show_modal_error("Set Quota", r);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function users_remove(elem) {
|
||||
var email = $(elem).parents('tr').attr('data-email');
|
||||
|
||||
@@ -293,7 +385,7 @@ function generate_random_password() {
|
||||
var charset = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; // confusable characters skipped
|
||||
for (var i = 0; i < 12; i++)
|
||||
pw += charset.charAt(Math.floor(Math.random() * charset.length));
|
||||
show_modal_error("Random Password", "<p>Here, try this:</p> <p><code style='font-size: 110%'>" + pw + "</code></pr");
|
||||
show_modal_error("Random Password", "<p>Here, try this:</p> <p><code style='font-size: 110%'>" + pw + "</code></p>");
|
||||
return false; // cancel click
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -51,9 +51,9 @@ if [[ $EUID -ne 0 ]]; then
|
||||
fi
|
||||
|
||||
# Clone the Mail-in-a-Box repository if it doesn't exist.
|
||||
if [ ! -d $HOME/mailinabox ]; then
|
||||
if [ ! -d "$HOME/mailinabox" ]; then
|
||||
if [ ! -f /usr/bin/git ]; then
|
||||
echo Installing git . . .
|
||||
echo "Installing git . . ."
|
||||
apt-get -q -q update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -q -q install -y git < /dev/null
|
||||
echo
|
||||
@@ -63,25 +63,25 @@ if [ ! -d $HOME/mailinabox ]; then
|
||||
SOURCE=https://github.com/mail-in-a-box/mailinabox
|
||||
fi
|
||||
|
||||
echo Downloading Mail-in-a-Box $TAG. . .
|
||||
echo "Downloading Mail-in-a-Box $TAG. . ."
|
||||
git clone \
|
||||
-b $TAG --depth 1 \
|
||||
$SOURCE \
|
||||
$HOME/mailinabox \
|
||||
-b "$TAG" --depth 1 \
|
||||
"$SOURCE" \
|
||||
"$HOME/mailinabox" \
|
||||
< /dev/null 2> /dev/null
|
||||
|
||||
echo
|
||||
fi
|
||||
|
||||
# Change directory to it.
|
||||
cd $HOME/mailinabox
|
||||
cd "$HOME/mailinabox" || exit
|
||||
|
||||
# Update it.
|
||||
if [ "$TAG" != $(git describe --always) ]; then
|
||||
echo Updating Mail-in-a-Box to $TAG . . .
|
||||
git fetch --depth 1 --force --prune origin tag $TAG
|
||||
if ! git checkout -q $TAG; then
|
||||
echo "Update failed. Did you modify something in $(pwd)?"
|
||||
if [ "$TAG" != "$(git describe --always)" ]; then
|
||||
echo "Updating Mail-in-a-Box to $TAG . . ."
|
||||
git fetch --depth 1 --force --prune origin tag "$TAG"
|
||||
if ! git checkout -q "$TAG"; then
|
||||
echo "Update failed. Did you modify something in $PWD?"
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
@@ -89,4 +89,3 @@ fi
|
||||
|
||||
# Start setup script.
|
||||
setup/start.sh
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@ source setup/functions.sh # load our functions
|
||||
source /etc/mailinabox.conf # load global vars
|
||||
|
||||
# Install DKIM...
|
||||
echo Installing OpenDKIM/OpenDMARC...
|
||||
echo "Installing OpenDKIM/OpenDMARC..."
|
||||
apt_install opendkim opendkim-tools opendmarc
|
||||
|
||||
# Make sure configuration directories exist.
|
||||
mkdir -p /etc/opendkim;
|
||||
mkdir -p $STORAGE_ROOT/mail/dkim
|
||||
mkdir -p "$STORAGE_ROOT/mail/dkim"
|
||||
|
||||
# Used in InternalHosts and ExternalIgnoreList configuration directives.
|
||||
# Not quite sure why.
|
||||
@@ -53,12 +53,12 @@ fi
|
||||
# such as Google. But they and others use a 2048 bit key, so we'll
|
||||
# do the same. Keys beyond 2048 bits may exceed DNS record limits.
|
||||
if [ ! -f "$STORAGE_ROOT/mail/dkim/mail.private" ]; then
|
||||
opendkim-genkey -b 2048 -r -s mail -D $STORAGE_ROOT/mail/dkim
|
||||
opendkim-genkey -b 2048 -r -s mail -D "$STORAGE_ROOT/mail/dkim"
|
||||
fi
|
||||
|
||||
# Ensure files are owned by the opendkim user and are private otherwise.
|
||||
chown -R opendkim:opendkim $STORAGE_ROOT/mail/dkim
|
||||
chmod go-rwx $STORAGE_ROOT/mail/dkim
|
||||
chown -R opendkim:opendkim "$STORAGE_ROOT/mail/dkim"
|
||||
chmod go-rwx "$STORAGE_ROOT/mail/dkim"
|
||||
|
||||
tools/editconf.py /etc/opendmarc.conf -s \
|
||||
"Syslog=true" \
|
||||
|
||||
@@ -106,7 +106,7 @@ if [ ! -f "$STORAGE_ROOT/dns/dnssec/$algo.conf" ]; then
|
||||
# (This previously used -b 2048 but it's unclear if this setting makes sense
|
||||
# for non-RSA keys, so it's removed. The RSA-based keys are not recommended
|
||||
# anymore anyway.)
|
||||
KSK=$(umask 077; cd $STORAGE_ROOT/dns/dnssec; ldns-keygen -r /dev/urandom -a $algo -k _domain_);
|
||||
KSK=$(umask 077; cd "$STORAGE_ROOT/dns/dnssec"; ldns-keygen -r /dev/urandom -a $algo -k _domain_);
|
||||
|
||||
# Now create a Zone-Signing Key (ZSK) which is expected to be
|
||||
# rotated more often than a KSK, although we have no plans to
|
||||
@@ -114,7 +114,7 @@ if [ ! -f "$STORAGE_ROOT/dns/dnssec/$algo.conf" ]; then
|
||||
# disturbing DNS availability.) Omit `-k`.
|
||||
# (This previously used -b 1024 but it's unclear if this setting makes sense
|
||||
# for non-RSA keys, so it's removed.)
|
||||
ZSK=$(umask 077; cd $STORAGE_ROOT/dns/dnssec; ldns-keygen -r /dev/urandom -a $algo _domain_);
|
||||
ZSK=$(umask 077; cd "$STORAGE_ROOT/dns/dnssec"; ldns-keygen -r /dev/urandom -a $algo _domain_);
|
||||
|
||||
# These generate two sets of files like:
|
||||
#
|
||||
@@ -126,7 +126,7 @@ if [ ! -f "$STORAGE_ROOT/dns/dnssec/$algo.conf" ]; then
|
||||
# options. So we'll store the names of the files we just generated.
|
||||
# We might have multiple keys down the road. This will identify
|
||||
# what keys are the current keys.
|
||||
cat > $STORAGE_ROOT/dns/dnssec/$algo.conf << EOF;
|
||||
cat > "$STORAGE_ROOT/dns/dnssec/$algo.conf" << EOF;
|
||||
KSK=$KSK
|
||||
ZSK=$ZSK
|
||||
EOF
|
||||
@@ -142,7 +142,7 @@ cat > /etc/cron.daily/mailinabox-dnssec << EOF;
|
||||
#!/bin/bash
|
||||
# Mail-in-a-Box
|
||||
# Re-sign any DNS zones with DNSSEC because the signatures expire periodically.
|
||||
$(pwd)/tools/dns_update
|
||||
$PWD/tools/dns_update
|
||||
EOF
|
||||
chmod +x /etc/cron.daily/mailinabox-dnssec
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
# If there aren't any mail users yet, create one.
|
||||
if [ -z "$(management/cli.py user)" ]; then
|
||||
# The outut of "management/cli.py user" is a list of mail users. If there
|
||||
@@ -10,7 +11,7 @@ if [ -z "$(management/cli.py user)" ]; then
|
||||
input_box "Mail Account" \
|
||||
"Let's create your first mail account.
|
||||
\n\nWhat email address do you want?" \
|
||||
me@$(get_default_hostname) \
|
||||
"me@$(get_default_hostname)" \
|
||||
EMAIL_ADDR
|
||||
|
||||
if [ -z "$EMAIL_ADDR" ]; then
|
||||
@@ -22,7 +23,7 @@ if [ -z "$(management/cli.py user)" ]; then
|
||||
input_box "Mail Account" \
|
||||
"That's not a valid email address.
|
||||
\n\nWhat email address do you want?" \
|
||||
$EMAIL_ADDR \
|
||||
"$EMAIL_ADDR" \
|
||||
EMAIL_ADDR
|
||||
if [ -z "$EMAIL_ADDR" ]; then
|
||||
# user hit ESC/cancel
|
||||
@@ -47,11 +48,11 @@ if [ -z "$(management/cli.py user)" ]; then
|
||||
fi
|
||||
|
||||
# Create the user's mail account. This will ask for a password if none was given above.
|
||||
management/cli.py user add $EMAIL_ADDR ${EMAIL_PW:-}
|
||||
management/cli.py user add "$EMAIL_ADDR" "${EMAIL_PW:-}"
|
||||
|
||||
# Make it an admin.
|
||||
hide_output management/cli.py user make-admin $EMAIL_ADDR
|
||||
hide_output management/cli.py user make-admin "$EMAIL_ADDR"
|
||||
|
||||
# Create an alias to which we'll direct all automatically-created administrative aliases.
|
||||
management/cli.py alias add administrator@$PRIMARY_HOSTNAME $EMAIL_ADDR > /dev/null
|
||||
management/cli.py alias add "administrator@$PRIMARY_HOSTNAME" "$EMAIL_ADDR" > /dev/null
|
||||
fi
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
# Turn on "strict mode." See http://redsymbol.net/articles/unofficial-bash-strict-mode/.
|
||||
# -e: exit if any command unexpectedly fails.
|
||||
# -u: exit if we have a variable typo.
|
||||
@@ -16,7 +17,7 @@ function hide_output {
|
||||
# Execute command, redirecting stderr/stdout to the temporary file. Since we
|
||||
# check the return code ourselves, disable 'set -e' temporarily.
|
||||
set +e
|
||||
"$@" &> $OUTPUT
|
||||
"$@" &> "$OUTPUT"
|
||||
E=$?
|
||||
set -e
|
||||
|
||||
@@ -24,15 +25,15 @@ function hide_output {
|
||||
if [ $E != 0 ]; then
|
||||
# Something failed.
|
||||
echo
|
||||
echo FAILED: "$@"
|
||||
echo "FAILED: $*"
|
||||
echo -----------------------------------------
|
||||
cat $OUTPUT
|
||||
cat "$OUTPUT"
|
||||
echo -----------------------------------------
|
||||
exit $E
|
||||
fi
|
||||
|
||||
# Remove temporary file.
|
||||
rm -f $OUTPUT
|
||||
rm -f "$OUTPUT"
|
||||
}
|
||||
|
||||
function apt_get_quiet {
|
||||
@@ -62,9 +63,9 @@ function get_default_hostname {
|
||||
# Guess the machine's hostname. It should be a fully qualified
|
||||
# domain name suitable for DNS. None of these calls may provide
|
||||
# the right value, but it's the best guess we can make.
|
||||
set -- $(hostname --fqdn 2>/dev/null ||
|
||||
set -- "$(hostname --fqdn 2>/dev/null ||
|
||||
hostname --all-fqdns 2>/dev/null ||
|
||||
hostname 2>/dev/null)
|
||||
hostname 2>/dev/null)"
|
||||
printf '%s\n' "$1" # return this value
|
||||
}
|
||||
|
||||
@@ -76,7 +77,7 @@ function get_publicip_from_web_service {
|
||||
#
|
||||
# Pass '4' or '6' as an argument to this function to specify
|
||||
# what type of address to get (IPv4, IPv6).
|
||||
curl -$1 --fail --silent --max-time 15 icanhazip.com 2>/dev/null || /bin/true
|
||||
curl -"$1" --fail --silent --max-time 15 icanhazip.com 2>/dev/null || /bin/true
|
||||
}
|
||||
|
||||
function get_default_privateip {
|
||||
@@ -119,19 +120,19 @@ function get_default_privateip {
|
||||
if [ "$1" == "6" ]; then target=2001:4860:4860::8888; fi
|
||||
|
||||
# Get the route information.
|
||||
route=$(ip -$1 -o route get $target 2>/dev/null | grep -v unreachable)
|
||||
route=$(ip -"$1" -o route get $target 2>/dev/null | grep -v unreachable)
|
||||
|
||||
# Parse the address out of the route information.
|
||||
address=$(echo $route | sed "s/.* src \([^ ]*\).*/\1/")
|
||||
address=$(echo "$route" | sed "s/.* src \([^ ]*\).*/\1/")
|
||||
|
||||
if [[ "$1" == "6" && $address == fe80:* ]]; then
|
||||
# For IPv6 link-local addresses, parse the interface out
|
||||
# of the route information and append it with a '%'.
|
||||
interface=$(echo $route | sed "s/.* dev \([^ ]*\).*/\1/")
|
||||
interface=$(echo "$route" | sed "s/.* dev \([^ ]*\).*/\1/")
|
||||
address=$address%$interface
|
||||
fi
|
||||
|
||||
echo $address
|
||||
echo "$address"
|
||||
}
|
||||
|
||||
function ufw_allow {
|
||||
@@ -149,7 +150,7 @@ function ufw_limit {
|
||||
}
|
||||
|
||||
function restart_service {
|
||||
hide_output service $1 restart
|
||||
hide_output service "$1" restart
|
||||
}
|
||||
|
||||
## Dialog Functions ##
|
||||
@@ -178,7 +179,7 @@ function input_menu {
|
||||
declare -n result_code=$4_EXITCODE
|
||||
local IFS=^$'\n'
|
||||
set +e
|
||||
result=$(dialog --stdout --title "$1" --menu "$2" 0 0 0 $3)
|
||||
result=$(dialog --stdout --title "$1" --menu "$2" 0 0 0 "$3")
|
||||
result_code=$?
|
||||
set -e
|
||||
}
|
||||
@@ -190,17 +191,17 @@ function wget_verify {
|
||||
HASH=$2
|
||||
DEST=$3
|
||||
CHECKSUM="$HASH $DEST"
|
||||
rm -f $DEST
|
||||
hide_output wget -O $DEST $URL
|
||||
rm -f "$DEST"
|
||||
hide_output wget -O "$DEST" "$URL"
|
||||
if ! echo "$CHECKSUM" | sha1sum --check --strict > /dev/null; then
|
||||
echo "------------------------------------------------------------"
|
||||
echo "Download of $URL did not match expected checksum."
|
||||
echo "Found:"
|
||||
sha1sum $DEST
|
||||
sha1sum "$DEST"
|
||||
echo
|
||||
echo "Expected:"
|
||||
echo "$CHECKSUM"
|
||||
rm -f $DEST
|
||||
rm -f "$DEST"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
@@ -216,9 +217,9 @@ function git_clone {
|
||||
SUBDIR=$3
|
||||
TARGETPATH=$4
|
||||
TMPPATH=/tmp/git-clone-$$
|
||||
rm -rf $TMPPATH $TARGETPATH
|
||||
git clone -q $REPO $TMPPATH || exit 1
|
||||
(cd $TMPPATH; git checkout -q $TREEISH;) || exit 1
|
||||
mv $TMPPATH/$SUBDIR $TARGETPATH
|
||||
rm -rf $TMPPATH "$TARGETPATH"
|
||||
git clone -q "$REPO" $TMPPATH || exit 1
|
||||
(cd $TMPPATH; git checkout -q "$TREEISH";) || exit 1
|
||||
mv $TMPPATH/"$SUBDIR" "$TARGETPATH"
|
||||
rm -rf $TMPPATH
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ apt_install \
|
||||
# - https://www.dovecot.org/list/dovecot/2012-August/137569.html
|
||||
# - https://www.dovecot.org/list/dovecot/2011-December/132455.html
|
||||
tools/editconf.py /etc/dovecot/conf.d/10-master.conf \
|
||||
default_process_limit=$(echo "$(nproc) * 250" | bc) \
|
||||
default_vsz_limit=$(echo "$(free -tm | tail -1 | awk '{print $2}') / 3" | bc)M \
|
||||
default_process_limit="$(($(nproc) * 250))" \
|
||||
default_vsz_limit="$(($(free -tm | tail -1 | awk '{print $2}') / 3))M" \
|
||||
log_path=/var/log/mail.log
|
||||
|
||||
# The inotify `max_user_instances` default is 128, which constrains
|
||||
@@ -61,12 +61,38 @@ tools/editconf.py /etc/sysctl.conf \
|
||||
# username part of the user's email address. We'll ensure that no bad domains or email addresses
|
||||
# are created within the management daemon.
|
||||
tools/editconf.py /etc/dovecot/conf.d/10-mail.conf \
|
||||
mail_location=maildir:$STORAGE_ROOT/mail/mailboxes/%d/%n \
|
||||
mail_location="maildir:$STORAGE_ROOT/mail/mailboxes/%d/%n" \
|
||||
mail_privileged_group=mail \
|
||||
first_valid_uid=0
|
||||
|
||||
# Create, subscribe, and mark as special folders: INBOX, Drafts, Sent, Trash, Spam and Archive.
|
||||
cp conf/dovecot-mailboxes.conf /etc/dovecot/conf.d/15-mailboxes.conf
|
||||
cp conf/dovecot/conf.d/15-mailboxes.conf /etc/dovecot/conf.d/
|
||||
sed -i "s/#mail_plugins = .*/mail_plugins = \$mail_plugins quota/" /etc/dovecot/conf.d/10-mail.conf
|
||||
if ! grep -q "mail_plugins = \$mail_plugins imap_quota" /etc/dovecot/conf.d/20-imap.conf; then
|
||||
sed -i "s/mail_plugins = .*/mail_plugins = \$mail_plugins imap_quota/" /etc/dovecot/conf.d/20-imap.conf
|
||||
fi
|
||||
|
||||
# configure stuff for quota support
|
||||
if ! grep -q "quota_status_success = DUNNO" /etc/dovecot/conf.d/90-quota.conf; then
|
||||
cat > /etc/dovecot/conf.d/90-quota.conf << EOF;
|
||||
plugin {
|
||||
quota = maildir
|
||||
|
||||
quota_grace = 10%
|
||||
|
||||
quota_status_success = DUNNO
|
||||
quota_status_nouser = DUNNO
|
||||
quota_status_overquota = "522 5.2.2 Mailbox is full"
|
||||
}
|
||||
|
||||
service quota-status {
|
||||
executable = quota-status -p postfix
|
||||
inet_listener {
|
||||
port = 12340
|
||||
}
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
# ### IMAP/POP
|
||||
|
||||
@@ -152,7 +178,7 @@ EOF
|
||||
# Setting a `postmaster_address` is required or LMTP won't start. An alias
|
||||
# will be created automatically by our management daemon.
|
||||
tools/editconf.py /etc/dovecot/conf.d/15-lda.conf \
|
||||
postmaster_address=postmaster@$PRIMARY_HOSTNAME
|
||||
"postmaster_address=postmaster@$PRIMARY_HOSTNAME"
|
||||
|
||||
# ### Sieve
|
||||
|
||||
@@ -201,14 +227,14 @@ chown -R mail:dovecot /etc/dovecot
|
||||
chmod -R o-rwx /etc/dovecot
|
||||
|
||||
# Ensure mailbox files have a directory that exists and are owned by the mail user.
|
||||
mkdir -p $STORAGE_ROOT/mail/mailboxes
|
||||
chown -R mail:mail $STORAGE_ROOT/mail/mailboxes
|
||||
mkdir -p "$STORAGE_ROOT/mail/mailboxes"
|
||||
chown -R mail:mail "$STORAGE_ROOT/mail/mailboxes"
|
||||
|
||||
# Same for the sieve scripts.
|
||||
mkdir -p $STORAGE_ROOT/mail/sieve
|
||||
mkdir -p $STORAGE_ROOT/mail/sieve/global_before
|
||||
mkdir -p $STORAGE_ROOT/mail/sieve/global_after
|
||||
chown -R mail:mail $STORAGE_ROOT/mail/sieve
|
||||
mkdir -p "$STORAGE_ROOT/mail/sieve"
|
||||
mkdir -p "$STORAGE_ROOT/mail/sieve/global_before"
|
||||
mkdir -p "$STORAGE_ROOT/mail/sieve/global_after"
|
||||
chown -R mail:mail "$STORAGE_ROOT/mail/sieve"
|
||||
|
||||
# Allow the IMAP/POP ports in the firewall.
|
||||
ufw_allow imaps
|
||||
|
||||
@@ -55,9 +55,9 @@ apt_install postfix postfix-sqlite postfix-pcre postgrey ca-certificates
|
||||
# * Set the SMTP banner (which must have the hostname first, then anything).
|
||||
tools/editconf.py /etc/postfix/main.cf \
|
||||
inet_interfaces=all \
|
||||
smtp_bind_address=$PRIVATE_IP \
|
||||
smtp_bind_address6=$PRIVATE_IPV6 \
|
||||
myhostname=$PRIMARY_HOSTNAME\
|
||||
smtp_bind_address="$PRIVATE_IP" \
|
||||
smtp_bind_address6="$PRIVATE_IPV6" \
|
||||
myhostname="$PRIMARY_HOSTNAME"\
|
||||
smtpd_banner="\$myhostname ESMTP Hi, I'm a Mail-in-a-Box (Ubuntu/Postfix; see https://mailinabox.email/)" \
|
||||
mydestination=localhost
|
||||
|
||||
@@ -138,9 +138,9 @@ sed -i "s/PUBLIC_IP/$PUBLIC_IP/" /etc/postfix/outgoing_mail_header_filters
|
||||
tools/editconf.py /etc/postfix/main.cf \
|
||||
smtpd_tls_security_level=may\
|
||||
smtpd_tls_auth_only=yes \
|
||||
smtpd_tls_cert_file=$STORAGE_ROOT/ssl/ssl_certificate.pem \
|
||||
smtpd_tls_key_file=$STORAGE_ROOT/ssl/ssl_private_key.pem \
|
||||
smtpd_tls_dh1024_param_file=$STORAGE_ROOT/ssl/dh2048.pem \
|
||||
smtpd_tls_cert_file="$STORAGE_ROOT/ssl/ssl_certificate.pem" \
|
||||
smtpd_tls_key_file="$STORAGE_ROOT/ssl/ssl_private_key.pem" \
|
||||
smtpd_tls_dh1024_param_file="$STORAGE_ROOT/ssl/dh2048.pem" \
|
||||
smtpd_tls_protocols="!SSLv2,!SSLv3" \
|
||||
smtpd_tls_ciphers=medium \
|
||||
tls_medium_cipherlist=ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA \
|
||||
@@ -238,7 +238,7 @@ tools/editconf.py /etc/postfix/main.cf -e lmtp_destination_recipient_limit=
|
||||
# "450 4.7.1 Client host rejected: Service unavailable". This is a retry code, so the mail doesn't properly bounce. #NODOC
|
||||
tools/editconf.py /etc/postfix/main.cf \
|
||||
smtpd_sender_restrictions="reject_non_fqdn_sender,reject_unknown_sender_domain,reject_authenticated_sender_login_mismatch,reject_rhsbl_sender dbl.spamhaus.org=127.0.1.[2..99]" \
|
||||
smtpd_recipient_restrictions="permit_sasl_authenticated,permit_mynetworks,reject_rbl_client zen.spamhaus.org=127.0.0.[2..11],reject_unlisted_recipient,check_policy_service inet:127.0.0.1:10023"
|
||||
smtpd_recipient_restrictions="permit_sasl_authenticated,permit_mynetworks,reject_rbl_client zen.spamhaus.org=127.0.0.[2..11],reject_unlisted_recipient,check_policy_service inet:127.0.0.1:10023,check_policy_service inet:127.0.0.1:12340"
|
||||
|
||||
# Postfix connects to Postgrey on the 127.0.0.1 interface specifically. Ensure that
|
||||
# Postgrey listens on the same interface (and not IPv6, for instance).
|
||||
@@ -260,17 +260,17 @@ tools/editconf.py /etc/default/postgrey \
|
||||
|
||||
|
||||
# If the $STORAGE_ROOT/mail/postgrey is empty, copy the postgrey database over from the old location
|
||||
if [ ! -d $STORAGE_ROOT/mail/postgrey/db ]; then
|
||||
if [ ! -d "$STORAGE_ROOT/mail/postgrey/db" ]; then
|
||||
# Stop the service
|
||||
service postgrey stop
|
||||
# Ensure the new paths for postgrey db exists
|
||||
mkdir -p $STORAGE_ROOT/mail/postgrey/db
|
||||
mkdir -p "$STORAGE_ROOT/mail/postgrey/db"
|
||||
# Move over database files
|
||||
mv /var/lib/postgrey/* $STORAGE_ROOT/mail/postgrey/db/ || true
|
||||
mv /var/lib/postgrey/* "$STORAGE_ROOT/mail/postgrey/db/" || true
|
||||
fi
|
||||
# Ensure permissions are set
|
||||
chown -R postgrey:postgrey $STORAGE_ROOT/mail/postgrey/
|
||||
chmod 700 $STORAGE_ROOT/mail/postgrey/{,db}
|
||||
chown -R postgrey:postgrey "$STORAGE_ROOT/mail/postgrey/"
|
||||
chmod 700 "$STORAGE_ROOT/mail/postgrey/"{,db}
|
||||
|
||||
# We are going to setup a newer whitelist for postgrey, the version included in the distribution is old
|
||||
cat > /etc/cron.daily/mailinabox-postgrey-whitelist << EOF;
|
||||
|
||||
@@ -18,12 +18,14 @@ source /etc/mailinabox.conf # load global vars
|
||||
db_path=$STORAGE_ROOT/mail/users.sqlite
|
||||
|
||||
# Create an empty database if it doesn't yet exist.
|
||||
if [ ! -f $db_path ]; then
|
||||
echo Creating new user database: $db_path;
|
||||
echo "CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT NOT NULL UNIQUE, password TEXT NOT NULL, extra, privileges TEXT NOT NULL DEFAULT '');" | sqlite3 $db_path;
|
||||
echo "CREATE TABLE aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, source TEXT NOT NULL UNIQUE, destination TEXT NOT NULL, permitted_senders TEXT);" | 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;
|
||||
if [ ! -f "$db_path" ]; then
|
||||
echo "Creating new user database: $db_path";
|
||||
echo "CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT NOT NULL UNIQUE, password TEXT NOT NULL, extra, privileges TEXT NOT NULL DEFAULT '', quota TEXT NOT NULL DEFAULT '0');" | sqlite3 $db_path;
|
||||
echo "CREATE TABLE aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, source TEXT NOT NULL UNIQUE, destination TEXT NOT NULL, permitted_senders TEXT);" | 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";
|
||||
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
|
||||
|
||||
# ### User Authentication
|
||||
@@ -51,7 +53,7 @@ driver = sqlite
|
||||
connect = $db_path
|
||||
default_pass_scheme = SHA512-CRYPT
|
||||
password_query = SELECT email as user, password FROM users WHERE email='%u';
|
||||
user_query = SELECT email AS user, "mail" as uid, "mail" as gid, "$STORAGE_ROOT/mail/mailboxes/%d/%n" as home FROM users WHERE email='%u';
|
||||
user_query = SELECT email AS user, "mail" as uid, "mail" as gid, "$STORAGE_ROOT/mail/mailboxes/%d/%n" as home, '*:bytes=' || quota AS quota_rule FROM users WHERE email='%u';
|
||||
iterate_query = SELECT email AS user FROM users;
|
||||
EOF
|
||||
chmod 0600 /etc/dovecot/dovecot-sql.conf.ext # per Dovecot instructions
|
||||
@@ -159,4 +161,5 @@ EOF
|
||||
restart_service postfix
|
||||
restart_service dovecot
|
||||
|
||||
|
||||
# force a recalculation of all user quotas
|
||||
doveadm quota recalc -A
|
||||
|
||||
@@ -52,9 +52,9 @@ hide_output $venv/bin/pip install --upgrade \
|
||||
# CONFIGURATION
|
||||
|
||||
# Create a backup directory and a random key for encrypting backups.
|
||||
mkdir -p $STORAGE_ROOT/backup
|
||||
if [ ! -f $STORAGE_ROOT/backup/secret_key.txt ]; then
|
||||
$(umask 077; openssl rand -base64 2048 > $STORAGE_ROOT/backup/secret_key.txt)
|
||||
mkdir -p "$STORAGE_ROOT/backup"
|
||||
if [ ! -f "$STORAGE_ROOT/backup/secret_key.txt" ]; then
|
||||
umask 077; openssl rand -base64 2048 > "$STORAGE_ROOT/backup/secret_key.txt"
|
||||
fi
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ tr -cd '[:xdigit:]' < /dev/urandom | head -c 32 > /var/lib/mailinabox/api.key
|
||||
chmod 640 /var/lib/mailinabox/api.key
|
||||
|
||||
source $venv/bin/activate
|
||||
export PYTHONPATH=$(pwd)/management
|
||||
export PYTHONPATH=$PWD/management
|
||||
exec gunicorn -b localhost:10222 -w 1 --timeout 630 wsgi:app
|
||||
EOF
|
||||
chmod +x $inst_dir/start
|
||||
@@ -116,7 +116,7 @@ minute=$((RANDOM % 60)) # avoid overloading mailinabox.email
|
||||
cat > /etc/cron.d/mailinabox-nightly << EOF;
|
||||
# Mail-in-a-Box --- Do not edit / will be overwritten on update.
|
||||
# Run nightly tasks: backup, status checks.
|
||||
$minute 3 * * * root (cd $(pwd) && management/daily_tasks.sh)
|
||||
$minute 3 * * * root (cd $PWD && management/daily_tasks.sh)
|
||||
EOF
|
||||
|
||||
# Start the management server.
|
||||
|
||||
@@ -40,7 +40,7 @@ chown munin /var/log/munin/munin-cgi-graph.log
|
||||
# ensure munin-node knows the name of this machine
|
||||
# and reduce logging level to warning
|
||||
tools/editconf.py /etc/munin/munin-node.conf -s \
|
||||
host_name=$PRIMARY_HOSTNAME \
|
||||
host_name="$PRIMARY_HOSTNAME" \
|
||||
log_level=1
|
||||
|
||||
# Update the activated plugins through munin's autoconfiguration.
|
||||
@@ -52,9 +52,9 @@ find /etc/munin/plugins/ -lname /usr/share/munin/plugins/ntp_ -print0 | xargs -0
|
||||
|
||||
# Deactivate monitoring of network interfaces that are not up. Otherwise we can get a lot of empty charts.
|
||||
for f in $(find /etc/munin/plugins/ \( -lname /usr/share/munin/plugins/if_ -o -lname /usr/share/munin/plugins/if_err_ -o -lname /usr/share/munin/plugins/bonding_err_ \)); do
|
||||
IF=$(echo $f | sed s/.*_//);
|
||||
if ! grep -qFx up /sys/class/net/$IF/operstate 2>/dev/null; then
|
||||
rm $f;
|
||||
IF=$(echo "$f" | sed s/.*_//);
|
||||
if ! grep -qFx up "/sys/class/net/$IF/operstate" 2>/dev/null; then
|
||||
rm "$f";
|
||||
fi;
|
||||
done
|
||||
|
||||
@@ -62,7 +62,7 @@ done
|
||||
mkdir -p /var/lib/munin-node/plugin-state/
|
||||
|
||||
# Create a systemd service for munin.
|
||||
ln -sf $(pwd)/management/munin_start.sh /usr/local/lib/mailinabox/munin_start.sh
|
||||
ln -sf "$PWD/management/munin_start.sh" /usr/local/lib/mailinabox/munin_start.sh
|
||||
chmod 0744 /usr/local/lib/mailinabox/munin_start.sh
|
||||
cp --remove-destination conf/munin.service /lib/systemd/system/munin.service # target was previously a symlink so remove first
|
||||
hide_output systemctl link -f /lib/systemd/system/munin.service
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
# Install the 'host', 'sed', and and 'nc' tools. This script is run before
|
||||
# the rest of the system setup so we may not yet have things installed.
|
||||
apt_get_quiet install bind9-host sed netcat-openbsd
|
||||
@@ -6,7 +7,7 @@ apt_get_quiet install bind9-host sed netcat-openbsd
|
||||
# The user might have chosen a name that was previously in use by a spammer
|
||||
# and will not be able to reliably send mail. Do this after any automatic
|
||||
# choices made above.
|
||||
if host $PRIMARY_HOSTNAME.dbl.spamhaus.org > /dev/null; then
|
||||
if host "$PRIMARY_HOSTNAME.dbl.spamhaus.org" > /dev/null; then
|
||||
echo
|
||||
echo "The hostname you chose '$PRIMARY_HOSTNAME' is listed in the"
|
||||
echo "Spamhaus Domain Block List. See http://www.spamhaus.org/dbl/"
|
||||
@@ -22,8 +23,8 @@ fi
|
||||
# The user might have ended up on an IP address that was previously in use
|
||||
# by a spammer, or the user may be deploying on a residential network. We
|
||||
# will not be able to reliably send mail in these cases.
|
||||
REVERSED_IPV4=$(echo $PUBLIC_IP | sed "s/\([0-9]*\).\([0-9]*\).\([0-9]*\).\([0-9]*\)/\4.\3.\2.\1/")
|
||||
if host $REVERSED_IPV4.zen.spamhaus.org > /dev/null; then
|
||||
REVERSED_IPV4=$(echo "$PUBLIC_IP" | sed "s/\([0-9]*\).\([0-9]*\).\([0-9]*\).\([0-9]*\)/\4.\3.\2.\1/")
|
||||
if host "$REVERSED_IPV4.zen.spamhaus.org" > /dev/null; then
|
||||
echo
|
||||
echo "The IP address $PUBLIC_IP is listed in the Spamhaus Block List."
|
||||
echo "See http://www.spamhaus.org/query/ip/$PUBLIC_IP."
|
||||
|
||||
@@ -65,13 +65,13 @@ user_external_hash=a494073dcdecbbbc79a9c77f72524ac9994d2eec
|
||||
# Clear prior packages and install dependencies from apt.
|
||||
apt-get purge -qq -y owncloud* # we used to use the package manager
|
||||
|
||||
apt_install curl php${PHP_VER} php${PHP_VER}-fpm \
|
||||
php${PHP_VER}-cli php${PHP_VER}-sqlite3 php${PHP_VER}-gd php${PHP_VER}-imap php${PHP_VER}-curl \
|
||||
php${PHP_VER}-dev php${PHP_VER}-gd php${PHP_VER}-xml php${PHP_VER}-mbstring php${PHP_VER}-zip php${PHP_VER}-apcu \
|
||||
php${PHP_VER}-intl php${PHP_VER}-imagick php${PHP_VER}-gmp php${PHP_VER}-bcmath
|
||||
apt_install curl php"${PHP_VER}" php"${PHP_VER}"-fpm \
|
||||
php"${PHP_VER}"-cli php"${PHP_VER}"-sqlite3 php"${PHP_VER}"-gd php"${PHP_VER}"-imap php"${PHP_VER}"-curl \
|
||||
php"${PHP_VER}"-dev php"${PHP_VER}"-gd php"${PHP_VER}"-xml php"${PHP_VER}"-mbstring php"${PHP_VER}"-zip php"${PHP_VER}"-apcu \
|
||||
php"${PHP_VER}"-intl php"${PHP_VER}"-imagick php"${PHP_VER}"-gmp php"${PHP_VER}"-bcmath
|
||||
|
||||
# Enable APC before Nextcloud tools are run.
|
||||
tools/editconf.py /etc/php/$PHP_VER/mods-available/apcu.ini -c ';' \
|
||||
tools/editconf.py /etc/php/"$PHP_VER"/mods-available/apcu.ini -c ';' \
|
||||
apc.enabled=1 \
|
||||
apc.enable_cli=1
|
||||
|
||||
@@ -91,7 +91,7 @@ InstallNextcloud() {
|
||||
echo
|
||||
|
||||
# Download and verify
|
||||
wget_verify https://download.nextcloud.com/server/releases/nextcloud-$version.zip $hash /tmp/nextcloud.zip
|
||||
wget_verify "https://download.nextcloud.com/server/releases/nextcloud-$version.zip" "$hash" /tmp/nextcloud.zip
|
||||
|
||||
# Remove the current owncloud/Nextcloud
|
||||
rm -rf /usr/local/lib/owncloud
|
||||
@@ -105,18 +105,18 @@ InstallNextcloud() {
|
||||
# their github repositories.
|
||||
mkdir -p /usr/local/lib/owncloud/apps
|
||||
|
||||
wget_verify https://github.com/nextcloud-releases/contacts/archive/refs/tags/v$version_contacts.tar.gz $hash_contacts /tmp/contacts.tgz
|
||||
wget_verify "https://github.com/nextcloud-releases/contacts/archive/refs/tags/v$version_contacts.tar.gz" "$hash_contacts" /tmp/contacts.tgz
|
||||
tar xf /tmp/contacts.tgz -C /usr/local/lib/owncloud/apps/
|
||||
rm /tmp/contacts.tgz
|
||||
|
||||
wget_verify https://github.com/nextcloud-releases/calendar/archive/refs/tags/v$version_calendar.tar.gz $hash_calendar /tmp/calendar.tgz
|
||||
wget_verify "https://github.com/nextcloud-releases/calendar/archive/refs/tags/v$version_calendar.tar.gz" "$hash_calendar" /tmp/calendar.tgz
|
||||
tar xf /tmp/calendar.tgz -C /usr/local/lib/owncloud/apps/
|
||||
rm /tmp/calendar.tgz
|
||||
|
||||
# Starting with Nextcloud 15, the app user_external is no longer included in Nextcloud core,
|
||||
# we will install from their github repository.
|
||||
if [ -n "$version_user_external" ]; then
|
||||
wget_verify https://github.com/nextcloud-releases/user_external/releases/download/v$version_user_external/user_external-v$version_user_external.tar.gz $hash_user_external /tmp/user_external.tgz
|
||||
wget_verify "https://github.com/nextcloud-releases/user_external/releases/download/v$version_user_external/user_external-v$version_user_external.tar.gz" "$hash_user_external" /tmp/user_external.tgz
|
||||
tar -xf /tmp/user_external.tgz -C /usr/local/lib/owncloud/apps/
|
||||
rm /tmp/user_external.tgz
|
||||
fi
|
||||
@@ -126,33 +126,35 @@ InstallNextcloud() {
|
||||
|
||||
# Create a symlink to the config.php in STORAGE_ROOT (for upgrades we're restoring the symlink we previously
|
||||
# put in, and in new installs we're creating a symlink and will create the actual config later).
|
||||
ln -sf $STORAGE_ROOT/owncloud/config.php /usr/local/lib/owncloud/config/config.php
|
||||
ln -sf "$STORAGE_ROOT/owncloud/config.php" /usr/local/lib/owncloud/config/config.php
|
||||
|
||||
# Make sure permissions are correct or the upgrade step won't run.
|
||||
# $STORAGE_ROOT/owncloud may not yet exist, so use -f to suppress
|
||||
# that error.
|
||||
chown -f -R www-data:www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud || /bin/true
|
||||
chown -f -R www-data:www-data "$STORAGE_ROOT/owncloud /usr/local/lib/owncloud" || /bin/true
|
||||
|
||||
# If this isn't a new installation, immediately run the upgrade script.
|
||||
# Then check for success (0=ok and 3=no upgrade needed, both are success).
|
||||
if [ -e $STORAGE_ROOT/owncloud/owncloud.db ]; then
|
||||
if [ -e "$STORAGE_ROOT/owncloud/owncloud.db" ]; then
|
||||
# ownCloud 8.1.1 broke upgrades. It may fail on the first attempt, but
|
||||
# that can be OK.
|
||||
sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/occ upgrade
|
||||
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then
|
||||
sudo -u www-data php"$PHP_VER" /usr/local/lib/owncloud/occ upgrade
|
||||
E=$?
|
||||
if [ $E -ne 0 ] && [ $E -ne 3 ]; then
|
||||
echo "Trying ownCloud upgrade again to work around ownCloud upgrade bug..."
|
||||
sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/occ upgrade
|
||||
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
|
||||
sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/occ maintenance:mode --off
|
||||
sudo -u www-data php"$PHP_VER" /usr/local/lib/owncloud/occ upgrade
|
||||
E=$?
|
||||
if [ $E -ne 0 ] && [ $E -ne 3 ]; then exit 1; fi
|
||||
sudo -u www-data php"$PHP_VER" /usr/local/lib/owncloud/occ maintenance:mode --off
|
||||
echo "...which seemed to work."
|
||||
fi
|
||||
|
||||
# Add missing indices. NextCloud didn't include this in the normal upgrade because it might take some time.
|
||||
sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/occ db:add-missing-indices
|
||||
sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/occ db:add-missing-primary-keys
|
||||
sudo -u www-data php"$PHP_VER" /usr/local/lib/owncloud/occ db:add-missing-indices
|
||||
sudo -u www-data php"$PHP_VER" /usr/local/lib/owncloud/occ db:add-missing-primary-keys
|
||||
|
||||
# Run conversion to BigInt identifiers, this process may take some time on large tables.
|
||||
sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/occ db:convert-filecache-bigint --no-interaction
|
||||
sudo -u www-data php"$PHP_VER" /usr/local/lib/owncloud/occ db:convert-filecache-bigint --no-interaction
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -164,7 +166,7 @@ InstallNextcloud() {
|
||||
|
||||
# If config.php exists, get version number, otherwise CURRENT_NEXTCLOUD_VER is empty.
|
||||
if [ -f "$STORAGE_ROOT/owncloud/config.php" ]; then
|
||||
CURRENT_NEXTCLOUD_VER=$(php$PHP_VER -r "include(\"$STORAGE_ROOT/owncloud/config.php\"); echo(\$CONFIG['version']);")
|
||||
CURRENT_NEXTCLOUD_VER=$(php"$PHP_VER" -r "include(\"$STORAGE_ROOT/owncloud/config.php\"); echo(\$CONFIG['version']);")
|
||||
else
|
||||
CURRENT_NEXTCLOUD_VER=""
|
||||
fi
|
||||
@@ -174,7 +176,7 @@ fi
|
||||
if [ ! -d /usr/local/lib/owncloud/ ] || [[ ! ${CURRENT_NEXTCLOUD_VER} =~ ^$nextcloud_ver ]]; then
|
||||
|
||||
# Stop php-fpm if running. If they are not running (which happens on a previously failed install), dont bail.
|
||||
service php$PHP_VER-fpm stop &> /dev/null || /bin/true
|
||||
service php"$PHP_VER"-fpm stop &> /dev/null || /bin/true
|
||||
|
||||
# Backup the existing ownCloud/Nextcloud.
|
||||
# Create a backup directory to store the current installation and database to
|
||||
@@ -184,21 +186,21 @@ if [ ! -d /usr/local/lib/owncloud/ ] || [[ ! ${CURRENT_NEXTCLOUD_VER} =~ ^$nextc
|
||||
echo "Upgrading Nextcloud --- backing up existing installation, configuration, and database to directory to $BACKUP_DIRECTORY..."
|
||||
cp -r /usr/local/lib/owncloud "$BACKUP_DIRECTORY/owncloud-install"
|
||||
fi
|
||||
if [ -e $STORAGE_ROOT/owncloud/owncloud.db ]; then
|
||||
cp $STORAGE_ROOT/owncloud/owncloud.db $BACKUP_DIRECTORY
|
||||
if [ -e "$STORAGE_ROOT/owncloud/owncloud.db" ]; then
|
||||
cp "$STORAGE_ROOT/owncloud/owncloud.db" "$BACKUP_DIRECTORY"
|
||||
fi
|
||||
if [ -e $STORAGE_ROOT/owncloud/config.php ]; then
|
||||
cp $STORAGE_ROOT/owncloud/config.php $BACKUP_DIRECTORY
|
||||
if [ -e "$STORAGE_ROOT/owncloud/config.php" ]; then
|
||||
cp "$STORAGE_ROOT/owncloud/config.php" "$BACKUP_DIRECTORY"
|
||||
fi
|
||||
|
||||
# If ownCloud or Nextcloud was previously installed....
|
||||
if [ ! -z ${CURRENT_NEXTCLOUD_VER} ]; then
|
||||
if [ -n "${CURRENT_NEXTCLOUD_VER}" ]; then
|
||||
# Database migrations from ownCloud are no longer possible because ownCloud cannot be run under
|
||||
# PHP 7.
|
||||
|
||||
if [ -e $STORAGE_ROOT/owncloud/config.php ]; then
|
||||
if [ -e "$STORAGE_ROOT/owncloud/config.php" ]; then
|
||||
# Remove the read-onlyness of the config, which is needed for migrations, especially for v24
|
||||
sed -i -e '/config_is_read_only/d' $STORAGE_ROOT/owncloud/config.php
|
||||
sed -i -e '/config_is_read_only/d' "$STORAGE_ROOT/owncloud/config.php"
|
||||
fi
|
||||
|
||||
if [[ ${CURRENT_NEXTCLOUD_VER} =~ ^[89] ]]; then
|
||||
@@ -246,13 +248,13 @@ fi
|
||||
|
||||
# Setup Nextcloud if the Nextcloud database does not yet exist. Running setup when
|
||||
# the database does exist wipes the database and user data.
|
||||
if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
|
||||
if [ ! -f "$STORAGE_ROOT/owncloud/owncloud.db" ]; then
|
||||
# Create user data directory
|
||||
mkdir -p $STORAGE_ROOT/owncloud
|
||||
mkdir -p "$STORAGE_ROOT/owncloud"
|
||||
|
||||
# Create an initial configuration file.
|
||||
instanceid=oc$(echo $PRIMARY_HOSTNAME | sha1sum | fold -w 10 | head -n 1)
|
||||
cat > $STORAGE_ROOT/owncloud/config.php <<EOF;
|
||||
instanceid=oc$(echo "$PRIMARY_HOSTNAME" | sha1sum | fold -w 10 | head -n 1)
|
||||
cat > "$STORAGE_ROOT/owncloud/config.php" <<EOF;
|
||||
<?php
|
||||
\$CONFIG = array (
|
||||
'datadirectory' => '$STORAGE_ROOT/owncloud',
|
||||
@@ -305,12 +307,12 @@ EOF
|
||||
EOF
|
||||
|
||||
# Set permissions
|
||||
chown -R www-data:www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
|
||||
chown -R www-data:www-data "$STORAGE_ROOT/owncloud" /usr/local/lib/owncloud
|
||||
|
||||
# Execute Nextcloud's setup step, which creates the Nextcloud sqlite database.
|
||||
# It also wipes it if it exists. And it updates config.php with database
|
||||
# settings and deletes the autoconfig.php file.
|
||||
(cd /usr/local/lib/owncloud; sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/index.php;)
|
||||
(cd /usr/local/lib/owncloud || exit; sudo -u www-data php"$PHP_VER" /usr/local/lib/owncloud/index.php;)
|
||||
fi
|
||||
|
||||
# Update config.php.
|
||||
@@ -326,7 +328,7 @@ fi
|
||||
# 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$PHP_VER <<EOF > $CONFIG_TEMP && mv $CONFIG_TEMP $STORAGE_ROOT/owncloud/config.php;
|
||||
php"$PHP_VER" <<EOF > "$CONFIG_TEMP" && mv "$CONFIG_TEMP" "$STORAGE_ROOT/owncloud/config.php";
|
||||
<?php
|
||||
include("$STORAGE_ROOT/owncloud/config.php");
|
||||
|
||||
@@ -357,31 +359,32 @@ var_export(\$CONFIG);
|
||||
echo ";";
|
||||
?>
|
||||
EOF
|
||||
chown www-data:www-data $STORAGE_ROOT/owncloud/config.php
|
||||
chown www-data:www-data "$STORAGE_ROOT/owncloud/config.php"
|
||||
|
||||
# Enable/disable apps. Note that this must be done after the Nextcloud setup.
|
||||
# The firstrunwizard gave Josh all sorts of problems, so disabling that.
|
||||
# user_external is what allows Nextcloud to use IMAP for login. The contacts
|
||||
# and calendar apps are the extensions we really care about here.
|
||||
hide_output sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/console.php app:disable firstrunwizard
|
||||
hide_output sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/console.php app:enable user_external
|
||||
hide_output sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/console.php app:enable contacts
|
||||
hide_output sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/console.php app:enable calendar
|
||||
hide_output sudo -u www-data php"$PHP_VER" /usr/local/lib/owncloud/console.php app:disable firstrunwizard
|
||||
hide_output sudo -u www-data php"$PHP_VER" /usr/local/lib/owncloud/console.php app:enable user_external
|
||||
hide_output sudo -u www-data php"$PHP_VER" /usr/local/lib/owncloud/console.php app:enable contacts
|
||||
hide_output sudo -u www-data php"$PHP_VER" /usr/local/lib/owncloud/console.php app:enable calendar
|
||||
|
||||
# When upgrading, run the upgrade script again now that apps are enabled. It seems like
|
||||
# the first upgrade at the top won't work because apps may be disabled during upgrade?
|
||||
# Check for success (0=ok, 3=no upgrade needed).
|
||||
sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/occ upgrade
|
||||
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
|
||||
sudo -u www-data php"$PHP_VER" /usr/local/lib/owncloud/occ upgrade
|
||||
E=$?
|
||||
if [ $E -ne 0 ] && [ $E -ne 3 ]; then exit 1; fi
|
||||
|
||||
# Disable default apps that we don't support
|
||||
sudo -u www-data \
|
||||
php$PHP_VER /usr/local/lib/owncloud/occ app:disable photos dashboard activity \
|
||||
php"$PHP_VER" /usr/local/lib/owncloud/occ app:disable photos dashboard activity \
|
||||
| (grep -v "No such app enabled" || /bin/true)
|
||||
|
||||
# Set PHP FPM values to support large file uploads
|
||||
# (semicolon is the comment character in this file, hashes produce deprecation warnings)
|
||||
tools/editconf.py /etc/php/$PHP_VER/fpm/php.ini -c ';' \
|
||||
tools/editconf.py /etc/php/"$PHP_VER"/fpm/php.ini -c ';' \
|
||||
upload_max_filesize=16G \
|
||||
post_max_size=16G \
|
||||
output_buffering=16384 \
|
||||
@@ -390,7 +393,7 @@ tools/editconf.py /etc/php/$PHP_VER/fpm/php.ini -c ';' \
|
||||
short_open_tag=On
|
||||
|
||||
# Set Nextcloud recommended opcache settings
|
||||
tools/editconf.py /etc/php/$PHP_VER/cli/conf.d/10-opcache.ini -c ';' \
|
||||
tools/editconf.py /etc/php/"$PHP_VER"/cli/conf.d/10-opcache.ini -c ';' \
|
||||
opcache.enable=1 \
|
||||
opcache.enable_cli=1 \
|
||||
opcache.interned_strings_buffer=8 \
|
||||
@@ -404,7 +407,7 @@ tools/editconf.py /etc/php/$PHP_VER/cli/conf.d/10-opcache.ini -c ';' \
|
||||
# This version was probably in use in Mail-in-a-Box v0.41 (February 26, 2019) and earlier.
|
||||
# We moved to v0.6.3 in 193763f8. Ignore errors - maybe there are duplicated users with the
|
||||
# correct backend already.
|
||||
sqlite3 $STORAGE_ROOT/owncloud/owncloud.db "UPDATE oc_users_external SET backend='127.0.0.1';" || /bin/true
|
||||
sqlite3 "$STORAGE_ROOT/owncloud/owncloud.db" "UPDATE oc_users_external SET backend='127.0.0.1';" || /bin/true
|
||||
|
||||
# Set up a general cron job for Nextcloud.
|
||||
# Also add another job for Calendar updates, per advice in the Nextcloud docs
|
||||
@@ -419,11 +422,11 @@ chmod +x /etc/cron.d/mailinabox-nextcloud
|
||||
|
||||
# We also need to change the sending mode from background-job to occ.
|
||||
# Or else the reminders will just be sent as soon as possible when the background jobs run.
|
||||
hide_output sudo -u www-data php$PHP_VER -f /usr/local/lib/owncloud/occ config:app:set dav sendEventRemindersMode --value occ
|
||||
hide_output sudo -u www-data php"$PHP_VER" -f /usr/local/lib/owncloud/occ config:app:set dav sendEventRemindersMode --value occ
|
||||
|
||||
# Now set the config to read-only.
|
||||
# Do this only at the very bottom when no further occ commands are needed.
|
||||
sed -i'' "s/'config_is_read_only'\s*=>\s*false/'config_is_read_only' => true/" $STORAGE_ROOT/owncloud/config.php
|
||||
sed -i'' "s/'config_is_read_only'\s*=>\s*false/'config_is_read_only' => true/" "$STORAGE_ROOT/owncloud/config.php"
|
||||
|
||||
# Rotate the nextcloud.log file
|
||||
cat > /etc/logrotate.d/nextcloud <<EOF
|
||||
@@ -448,4 +451,4 @@ EOF
|
||||
# ```
|
||||
|
||||
# Enable PHP modules and restart PHP.
|
||||
restart_service php$PHP_VER-fpm
|
||||
restart_service php"$PHP_VER"-fpm
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
if [ -z "${NONINTERACTIVE:-}" ]; then
|
||||
# Install 'dialog' so we can ask the user questions. The original motivation for
|
||||
# this was being able to ask the user for input even if stdin has been redirected,
|
||||
@@ -7,7 +8,7 @@ if [ -z "${NONINTERACTIVE:-}" ]; then
|
||||
#
|
||||
# Also install dependencies needed to validate the email address.
|
||||
if [ ! -f /usr/bin/dialog ] || [ ! -f /usr/bin/python3 ] || [ ! -f /usr/bin/pip3 ]; then
|
||||
echo Installing packages needed for setup...
|
||||
echo "Installing packages needed for setup..."
|
||||
apt-get -q -q update
|
||||
apt_get_quiet install dialog python3 python3-pip || exit 1
|
||||
fi
|
||||
@@ -31,7 +32,7 @@ if [ -z "${PRIMARY_HOSTNAME:-}" ]; then
|
||||
# domain the user possibly wants to use is example.com then.
|
||||
# We strip the string "box." from the hostname to get the mail
|
||||
# domain. If the hostname differs, nothing happens here.
|
||||
DEFAULT_DOMAIN_GUESS=$(echo $(get_default_hostname) | sed -e 's/^box\.//')
|
||||
DEFAULT_DOMAIN_GUESS=$(get_default_hostname | sed -e 's/^box\.//')
|
||||
|
||||
# This is the first run. Ask the user for his email address so we can
|
||||
# provide the best default for the box's hostname.
|
||||
@@ -55,7 +56,7 @@ you really want.
|
||||
do
|
||||
input_box "Your Email Address" \
|
||||
"That's not a valid email address.\n\nWhat email address are you setting this box up to manage?" \
|
||||
$EMAIL_ADDR \
|
||||
"$EMAIL_ADDR" \
|
||||
EMAIL_ADDR
|
||||
if [ -z "$EMAIL_ADDR" ]; then
|
||||
# user hit ESC/cancel
|
||||
@@ -65,7 +66,7 @@ you really want.
|
||||
|
||||
# Take the part after the @-sign as the user's domain name, and add
|
||||
# 'box.' to the beginning to create a default hostname for this machine.
|
||||
DEFAULT_PRIMARY_HOSTNAME=box.$(echo $EMAIL_ADDR | sed 's/.*@//')
|
||||
DEFAULT_PRIMARY_HOSTNAME=box.$(echo "$EMAIL_ADDR" | sed 's/.*@//')
|
||||
fi
|
||||
|
||||
input_box "Hostname" \
|
||||
@@ -74,7 +75,7 @@ you really want.
|
||||
address, so we're suggesting $DEFAULT_PRIMARY_HOSTNAME.
|
||||
\n\nYou can change it, but we recommend you don't.
|
||||
\n\nHostname:" \
|
||||
$DEFAULT_PRIMARY_HOSTNAME \
|
||||
"$DEFAULT_PRIMARY_HOSTNAME" \
|
||||
PRIMARY_HOSTNAME
|
||||
|
||||
if [ -z "$PRIMARY_HOSTNAME" ]; then
|
||||
@@ -92,7 +93,7 @@ if [ -z "${PUBLIC_IP:-}" ]; then
|
||||
|
||||
# On the first run, if we got an answer from the Internet then don't
|
||||
# ask the user.
|
||||
if [[ -z "${DEFAULT_PUBLIC_IP:-}" && ! -z "$GUESSED_IP" ]]; then
|
||||
if [[ -z "${DEFAULT_PUBLIC_IP:-}" && -n "$GUESSED_IP" ]]; then
|
||||
PUBLIC_IP=$GUESSED_IP
|
||||
|
||||
# Otherwise on the first run at least provide a default.
|
||||
@@ -109,7 +110,7 @@ if [ -z "${PUBLIC_IP:-}" ]; then
|
||||
input_box "Public IP Address" \
|
||||
"Enter the public IP address of this machine, as given to you by your ISP.
|
||||
\n\nPublic IP address:" \
|
||||
${DEFAULT_PUBLIC_IP:-} \
|
||||
"${DEFAULT_PUBLIC_IP:-}" \
|
||||
PUBLIC_IP
|
||||
|
||||
if [ -z "$PUBLIC_IP" ]; then
|
||||
@@ -125,7 +126,7 @@ if [ -z "${PUBLIC_IPV6:-}" ]; then
|
||||
# Ask the Internet.
|
||||
GUESSED_IP=$(get_publicip_from_web_service 6)
|
||||
MATCHED=0
|
||||
if [[ -z "${DEFAULT_PUBLIC_IPV6:-}" && ! -z "$GUESSED_IP" ]]; then
|
||||
if [[ -z "${DEFAULT_PUBLIC_IPV6:-}" && -n "$GUESSED_IP" ]]; then
|
||||
PUBLIC_IPV6=$GUESSED_IP
|
||||
elif [[ "${DEFAULT_PUBLIC_IPV6:-}" == "$GUESSED_IP" ]]; then
|
||||
# No IPv6 entered and machine seems to have none, or what
|
||||
@@ -141,10 +142,10 @@ if [ -z "${PUBLIC_IPV6:-}" ]; then
|
||||
"Enter the public IPv6 address of this machine, as given to you by your ISP.
|
||||
\n\nLeave blank if the machine does not have an IPv6 address.
|
||||
\n\nPublic IPv6 address:" \
|
||||
${DEFAULT_PUBLIC_IPV6:-} \
|
||||
"${DEFAULT_PUBLIC_IPV6:-}" \
|
||||
PUBLIC_IPV6
|
||||
|
||||
if [ ! $PUBLIC_IPV6_EXITCODE ]; then
|
||||
if [ ! -n "$PUBLIC_IPV6_EXITCODE" ]; then
|
||||
# user hit ESC/cancel
|
||||
exit
|
||||
fi
|
||||
@@ -197,7 +198,7 @@ fi
|
||||
echo
|
||||
echo "Primary Hostname: $PRIMARY_HOSTNAME"
|
||||
echo "Public IP Address: $PUBLIC_IP"
|
||||
if [ ! -z "$PUBLIC_IPV6" ]; then
|
||||
if [ -n "$PUBLIC_IPV6" ]; then
|
||||
echo "Public IPv6 Address: $PUBLIC_IPV6"
|
||||
fi
|
||||
if [ "$PRIVATE_IP" != "$PUBLIC_IP" ]; then
|
||||
@@ -207,6 +208,6 @@ if [ "$PRIVATE_IPV6" != "$PUBLIC_IPV6" ]; then
|
||||
echo "Private IPv6 Address: $PRIVATE_IPV6"
|
||||
fi
|
||||
if [ -f /usr/bin/git ] && [ -d .git ]; then
|
||||
echo "Mail-in-a-Box Version: " $(git describe --always)
|
||||
echo "Mail-in-a-Box Version: $(git describe --always)"
|
||||
fi
|
||||
echo
|
||||
|
||||
@@ -135,11 +135,11 @@ EOF
|
||||
# the filemode in the config file.
|
||||
|
||||
tools/editconf.py /etc/spamassassin/local.cf -s \
|
||||
bayes_path=$STORAGE_ROOT/mail/spamassassin/bayes \
|
||||
bayes_path="$STORAGE_ROOT/mail/spamassassin/bayes" \
|
||||
bayes_file_mode=0666
|
||||
|
||||
mkdir -p $STORAGE_ROOT/mail/spamassassin
|
||||
chown -R spampd:spampd $STORAGE_ROOT/mail/spamassassin
|
||||
mkdir -p "$STORAGE_ROOT/mail/spamassassin"
|
||||
chown -R spampd:spampd "$STORAGE_ROOT/mail/spamassassin"
|
||||
|
||||
# To mark mail as spam or ham, just drag it in or out of the Spam folder. We'll
|
||||
# use the Dovecot antispam plugin to detect the message move operation and execute
|
||||
@@ -184,8 +184,8 @@ chmod a+x /usr/local/bin/sa-learn-pipe.sh
|
||||
# Create empty bayes training data (if it doesn't exist). Once the files exist,
|
||||
# ensure they are group-writable so that the Dovecot process has access.
|
||||
sudo -u spampd /usr/bin/sa-learn --sync 2>/dev/null
|
||||
chmod -R 660 $STORAGE_ROOT/mail/spamassassin
|
||||
chmod 770 $STORAGE_ROOT/mail/spamassassin
|
||||
chmod -R 660 "$STORAGE_ROOT/mail/spamassassin"
|
||||
chmod 770 "$STORAGE_ROOT/mail/spamassassin"
|
||||
|
||||
# Initial training?
|
||||
# sa-learn --ham storage/mail/mailboxes/*/*/cur/
|
||||
|
||||
24
setup/ssl.sh
24
setup/ssl.sh
@@ -26,9 +26,9 @@ source /etc/mailinabox.conf # load global vars
|
||||
|
||||
# Show a status line if we are going to take any action in this file.
|
||||
if [ ! -f /usr/bin/openssl ] \
|
||||
|| [ ! -f $STORAGE_ROOT/ssl/ssl_private_key.pem ] \
|
||||
|| [ ! -f $STORAGE_ROOT/ssl/ssl_certificate.pem ] \
|
||||
|| [ ! -f $STORAGE_ROOT/ssl/dh2048.pem ]; then
|
||||
|| [ ! -f "$STORAGE_ROOT/ssl/ssl_private_key.pem" ] \
|
||||
|| [ ! -f "$STORAGE_ROOT/ssl/ssl_certificate.pem" ] \
|
||||
|| [ ! -f "$STORAGE_ROOT/ssl/dh2048.pem" ]; then
|
||||
echo "Creating initial SSL certificate and perfect forward secrecy Diffie-Hellman parameters..."
|
||||
fi
|
||||
|
||||
@@ -38,7 +38,7 @@ apt_install openssl
|
||||
|
||||
# Create a directory to store TLS-related things like "SSL" certificates.
|
||||
|
||||
mkdir -p $STORAGE_ROOT/ssl
|
||||
mkdir -p "$STORAGE_ROOT/ssl"
|
||||
|
||||
# Generate a new private key.
|
||||
#
|
||||
@@ -60,39 +60,39 @@ mkdir -p $STORAGE_ROOT/ssl
|
||||
#
|
||||
# Since we properly seed /dev/urandom in system.sh we should be fine, but I leave
|
||||
# in the rest of the notes in case that ever changes.
|
||||
if [ ! -f $STORAGE_ROOT/ssl/ssl_private_key.pem ]; then
|
||||
if [ ! -f "$STORAGE_ROOT/ssl/ssl_private_key.pem" ]; then
|
||||
# Set the umask so the key file is never world-readable.
|
||||
(umask 077; hide_output \
|
||||
openssl genrsa -out $STORAGE_ROOT/ssl/ssl_private_key.pem 2048)
|
||||
openssl genrsa -out "$STORAGE_ROOT/ssl/ssl_private_key.pem" 2048)
|
||||
fi
|
||||
|
||||
# Generate a self-signed SSL certificate because things like nginx, dovecot,
|
||||
# etc. won't even start without some certificate in place, and we need nginx
|
||||
# so we can offer the user a control panel to install a better certificate.
|
||||
if [ ! -f $STORAGE_ROOT/ssl/ssl_certificate.pem ]; then
|
||||
if [ ! -f "$STORAGE_ROOT/ssl/ssl_certificate.pem" ]; then
|
||||
# Generate a certificate signing request.
|
||||
CSR=/tmp/ssl_cert_sign_req-$$.csr
|
||||
hide_output \
|
||||
openssl req -new -key $STORAGE_ROOT/ssl/ssl_private_key.pem -out $CSR \
|
||||
openssl req -new -key "$STORAGE_ROOT/ssl/ssl_private_key.pem" -out $CSR \
|
||||
-sha256 -subj "/CN=$PRIMARY_HOSTNAME"
|
||||
|
||||
# Generate the self-signed certificate.
|
||||
CERT=$STORAGE_ROOT/ssl/$PRIMARY_HOSTNAME-selfsigned-$(date --rfc-3339=date | sed s/-//g).pem
|
||||
hide_output \
|
||||
openssl x509 -req -days 365 \
|
||||
-in $CSR -signkey $STORAGE_ROOT/ssl/ssl_private_key.pem -out $CERT
|
||||
-in $CSR -signkey "$STORAGE_ROOT/ssl/ssl_private_key.pem" -out "$CERT"
|
||||
|
||||
# Delete the certificate signing request because it has no other purpose.
|
||||
rm -f $CSR
|
||||
|
||||
# Symlink the certificate into the system certificate path, so system services
|
||||
# can find it.
|
||||
ln -s $CERT $STORAGE_ROOT/ssl/ssl_certificate.pem
|
||||
ln -s "$CERT" "$STORAGE_ROOT/ssl/ssl_certificate.pem"
|
||||
fi
|
||||
|
||||
# Generate some Diffie-Hellman cipher bits.
|
||||
# openssl's default bit length for this is 1024 bits, but we'll create
|
||||
# 2048 bits of bits per the latest recommendations.
|
||||
if [ ! -f $STORAGE_ROOT/ssl/dh2048.pem ]; then
|
||||
openssl dhparam -out $STORAGE_ROOT/ssl/dh2048.pem 2048
|
||||
if [ ! -f "$STORAGE_ROOT/ssl/dh2048.pem" ]; then
|
||||
openssl dhparam -out "$STORAGE_ROOT/ssl/dh2048.pem" 2048
|
||||
fi
|
||||
|
||||
@@ -46,7 +46,7 @@ fi
|
||||
# in the first dialog prompt, so we should do this before that starts.
|
||||
cat > /usr/local/bin/mailinabox << EOF;
|
||||
#!/bin/bash
|
||||
cd $(pwd)
|
||||
cd $PWD
|
||||
source setup/start.sh
|
||||
EOF
|
||||
chmod +x /usr/local/bin/mailinabox
|
||||
@@ -75,17 +75,17 @@ fi
|
||||
# migration (schema) number for the files stored there, assume this is a fresh
|
||||
# installation to that directory and write the file to contain the current
|
||||
# migration number for this version of Mail-in-a-Box.
|
||||
if ! id -u $STORAGE_USER >/dev/null 2>&1; then
|
||||
useradd -m $STORAGE_USER
|
||||
if ! id -u "$STORAGE_USER" >/dev/null 2>&1; then
|
||||
useradd -m "$STORAGE_USER"
|
||||
fi
|
||||
if [ ! -d $STORAGE_ROOT ]; then
|
||||
mkdir -p $STORAGE_ROOT
|
||||
if [ ! -d "$STORAGE_ROOT" ]; then
|
||||
mkdir -p "$STORAGE_ROOT"
|
||||
fi
|
||||
f=$STORAGE_ROOT
|
||||
while [[ $f != / ]]; do chmod a+rx "$f"; f=$(dirname "$f"); done;
|
||||
if [ ! -f $STORAGE_ROOT/mailinabox.version ]; then
|
||||
setup/migrate.py --current > $STORAGE_ROOT/mailinabox.version
|
||||
chown $STORAGE_USER:$STORAGE_USER $STORAGE_ROOT/mailinabox.version
|
||||
if [ ! -f "$STORAGE_ROOT/mailinabox.version" ]; then
|
||||
setup/migrate.py --current > "$STORAGE_ROOT/mailinabox.version"
|
||||
chown "$STORAGE_USER:$STORAGE_USER" "$STORAGE_ROOT/mailinabox.version"
|
||||
fi
|
||||
|
||||
# Save the global options in /etc/mailinabox.conf so that standalone
|
||||
@@ -122,7 +122,7 @@ source setup/munin.sh
|
||||
# Wait for the management daemon to start...
|
||||
until nc -z -w 4 127.0.0.1 10222
|
||||
do
|
||||
echo Waiting for the Mail-in-a-Box management daemon to start...
|
||||
echo "Waiting for the Mail-in-a-Box management daemon to start..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
@@ -142,41 +142,41 @@ source setup/firstuser.sh
|
||||
# We'd let certbot ask the user interactively, but when this script is
|
||||
# run in the recommended curl-pipe-to-bash method there is no TTY and
|
||||
# certbot will fail if it tries to ask.
|
||||
if [ ! -d $STORAGE_ROOT/ssl/lets_encrypt/accounts/acme-v02.api.letsencrypt.org/ ]; then
|
||||
if [ ! -d "$STORAGE_ROOT/ssl/lets_encrypt/accounts/acme-v02.api.letsencrypt.org/" ]; then
|
||||
echo
|
||||
echo "-----------------------------------------------"
|
||||
echo "Mail-in-a-Box uses Let's Encrypt to provision free SSL/TLS certificates"
|
||||
echo "to enable HTTPS connections to your box. We're automatically"
|
||||
echo "agreeing you to their subscriber agreement. See https://letsencrypt.org."
|
||||
echo
|
||||
certbot register --register-unsafely-without-email --agree-tos --config-dir $STORAGE_ROOT/ssl/lets_encrypt
|
||||
certbot register --register-unsafely-without-email --agree-tos --config-dir "$STORAGE_ROOT/ssl/lets_encrypt"
|
||||
fi
|
||||
|
||||
# Done.
|
||||
echo
|
||||
echo "-----------------------------------------------"
|
||||
echo
|
||||
echo Your Mail-in-a-Box is running.
|
||||
echo "Your Mail-in-a-Box is running."
|
||||
echo
|
||||
echo Please log in to the control panel for further instructions at:
|
||||
echo "Please log in to the control panel for further instructions at:"
|
||||
echo
|
||||
if management/status_checks.py --check-primary-hostname; then
|
||||
# Show the nice URL if it appears to be resolving and has a valid certificate.
|
||||
echo https://$PRIMARY_HOSTNAME/admin
|
||||
echo "https://$PRIMARY_HOSTNAME/admin"
|
||||
echo
|
||||
echo "If you have a DNS problem put the box's IP address in the URL"
|
||||
echo "(https://$PUBLIC_IP/admin) but then check the TLS fingerprint:"
|
||||
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint -sha256\
|
||||
openssl x509 -in "$STORAGE_ROOT/ssl/ssl_certificate.pem" -noout -fingerprint -sha256\
|
||||
| sed "s/SHA256 Fingerprint=//i"
|
||||
else
|
||||
echo https://$PUBLIC_IP/admin
|
||||
echo "https://$PUBLIC_IP/admin"
|
||||
echo
|
||||
echo You will be alerted that the website has an invalid certificate. Check that
|
||||
echo the certificate fingerprint matches:
|
||||
echo "You will be alerted that the website has an invalid certificate. Check that"
|
||||
echo "the certificate fingerprint matches:"
|
||||
echo
|
||||
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint -sha256\
|
||||
openssl x509 -in "$STORAGE_ROOT/ssl/ssl_certificate.pem" -noout -fingerprint -sha256\
|
||||
| sed "s/SHA256 Fingerprint=//i"
|
||||
echo
|
||||
echo Then you can confirm the security exception and continue.
|
||||
echo "Then you can confirm the security exception and continue."
|
||||
echo
|
||||
fi
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
source /etc/mailinabox.conf
|
||||
source setup/functions.sh # load our functions
|
||||
|
||||
@@ -11,8 +12,8 @@ source setup/functions.sh # load our functions
|
||||
#
|
||||
# First set the hostname in the configuration file, then activate the setting
|
||||
|
||||
echo $PRIMARY_HOSTNAME > /etc/hostname
|
||||
hostname $PRIMARY_HOSTNAME
|
||||
echo "$PRIMARY_HOSTNAME" > /etc/hostname
|
||||
hostname "$PRIMARY_HOSTNAME"
|
||||
|
||||
# ### Fix permissions
|
||||
|
||||
@@ -53,14 +54,14 @@ if
|
||||
[ -z "$SWAP_IN_FSTAB" ] &&
|
||||
[ ! -e /swapfile ] &&
|
||||
[ -z "$ROOT_IS_BTRFS" ] &&
|
||||
[ $TOTAL_PHYSICAL_MEM -lt 1900000 ] &&
|
||||
[ $AVAILABLE_DISK_SPACE -gt 5242880 ]
|
||||
[ "$TOTAL_PHYSICAL_MEM" -lt 1900000 ] &&
|
||||
[ "$AVAILABLE_DISK_SPACE" -gt 5242880 ]
|
||||
then
|
||||
echo "Adding a swap file to the system..."
|
||||
|
||||
# Allocate and activate the swap file. Allocate in 1KB chuncks
|
||||
# doing it in one go, could fail on low memory systems
|
||||
dd if=/dev/zero of=/swapfile bs=1024 count=$[1024*1024] status=none
|
||||
dd if=/dev/zero of=/swapfile bs=1024 count=$((1024*1024)) status=none
|
||||
if [ -e /swapfile ]; then
|
||||
chmod 600 /swapfile
|
||||
hide_output mkswap /swapfile
|
||||
@@ -110,7 +111,7 @@ hide_output add-apt-repository --y ppa:ondrej/php
|
||||
# of things from Ubuntu, as well as the directory of packages provide by the
|
||||
# PPAs so we can install those packages later.
|
||||
|
||||
echo Updating system packages...
|
||||
echo "Updating system packages..."
|
||||
hide_output apt-get update
|
||||
apt_get_quiet upgrade
|
||||
|
||||
@@ -135,7 +136,7 @@ apt_get_quiet autoremove
|
||||
# * bc: allows us to do math to compute sane defaults
|
||||
# * openssh-client: provides ssh-keygen
|
||||
|
||||
echo Installing system packages...
|
||||
echo "Installing system packages..."
|
||||
apt_install python3 python3-dev python3-pip python3-setuptools \
|
||||
netcat-openbsd wget curl git sudo coreutils bc file \
|
||||
pollinate openssh-client unzip \
|
||||
@@ -164,7 +165,7 @@ fi
|
||||
# not likely the user will want to change this, so we only ask on first
|
||||
# setup.
|
||||
if [ -z "${NONINTERACTIVE:-}" ]; then
|
||||
if [ ! -f /etc/timezone ] || [ ! -z ${FIRST_TIME_SETUP:-} ]; then
|
||||
if [ ! -f /etc/timezone ] || [ -n "${FIRST_TIME_SETUP:-}" ]; then
|
||||
# If the file is missing or this is the user's first time running
|
||||
# Mail-in-a-Box setup, run the interactive timezone configuration
|
||||
# tool.
|
||||
@@ -226,7 +227,7 @@ fi
|
||||
# hardware entropy to get going, by drawing from /dev/random. haveged makes this
|
||||
# less likely to stall for very long.
|
||||
|
||||
echo Initializing system random number generator...
|
||||
echo "Initializing system random number generator..."
|
||||
dd if=/dev/random of=/dev/urandom bs=1 count=32 2> /dev/null
|
||||
|
||||
# This is supposedly sufficient. But because we're not sure if hardware entropy
|
||||
@@ -270,11 +271,11 @@ if [ -z "${DISABLE_FIREWALL:-}" ]; then
|
||||
# settings, find the port it is supposedly running on, and open that port #NODOC
|
||||
# too. #NODOC
|
||||
SSH_PORT=$(sshd -T 2>/dev/null | grep "^port " | sed "s/port //") #NODOC
|
||||
if [ ! -z "$SSH_PORT" ]; then
|
||||
if [ -n "$SSH_PORT" ]; then
|
||||
if [ "$SSH_PORT" != "22" ]; then
|
||||
|
||||
echo Opening alternate SSH port $SSH_PORT. #NODOC
|
||||
ufw_limit $SSH_PORT #NODOC
|
||||
echo "Opening alternate SSH port $SSH_PORT." #NODOC
|
||||
ufw_limit "$SSH_PORT" #NODOC
|
||||
|
||||
fi
|
||||
fi
|
||||
|
||||
38
setup/web.sh
38
setup/web.sh
@@ -8,7 +8,7 @@ source /etc/mailinabox.conf # load global vars
|
||||
# Some Ubuntu images start off with Apache. Remove it since we
|
||||
# will use nginx. Use autoremove to remove any Apache depenencies.
|
||||
if [ -f /usr/sbin/apache2 ]; then
|
||||
echo Removing apache...
|
||||
echo "Removing apache..."
|
||||
hide_output apt-get -y purge apache2 apache2-*
|
||||
hide_output apt-get -y --purge autoremove
|
||||
fi
|
||||
@@ -19,7 +19,7 @@ fi
|
||||
|
||||
echo "Installing Nginx (web server)..."
|
||||
|
||||
apt_install nginx php${PHP_VER}-cli php${PHP_VER}-fpm idn2
|
||||
apt_install nginx php"${PHP_VER}"-cli php"${PHP_VER}"-fpm idn2
|
||||
|
||||
rm -f /etc/nginx/sites-enabled/default
|
||||
|
||||
@@ -46,15 +46,15 @@ tools/editconf.py /etc/nginx/nginx.conf -s \
|
||||
ssl_protocols="TLSv1.2 TLSv1.3;"
|
||||
|
||||
# Tell PHP not to expose its version number in the X-Powered-By header.
|
||||
tools/editconf.py /etc/php/$PHP_VER/fpm/php.ini -c ';' \
|
||||
tools/editconf.py /etc/php/"$PHP_VER"/fpm/php.ini -c ';' \
|
||||
expose_php=Off
|
||||
|
||||
# Set PHPs default charset to UTF-8, since we use it. See #367.
|
||||
tools/editconf.py /etc/php/$PHP_VER/fpm/php.ini -c ';' \
|
||||
tools/editconf.py /etc/php/"$PHP_VER"/fpm/php.ini -c ';' \
|
||||
default_charset="UTF-8"
|
||||
|
||||
# Configure the path environment for php-fpm
|
||||
tools/editconf.py /etc/php/$PHP_VER/fpm/pool.d/www.conf -c ';' \
|
||||
tools/editconf.py /etc/php/"$PHP_VER"/fpm/pool.d/www.conf -c ';' \
|
||||
env[PATH]=/usr/local/bin:/usr/bin:/bin \
|
||||
|
||||
# Configure php-fpm based on the amount of memory the machine has
|
||||
@@ -62,32 +62,32 @@ tools/editconf.py /etc/php/$PHP_VER/fpm/pool.d/www.conf -c ';' \
|
||||
# Some synchronisation issues can occur when many people access the site at once.
|
||||
# The pm=ondemand setting is used for memory constrained machines < 2GB, this is copied over from PR: 1216
|
||||
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}' || /bin/true)
|
||||
if [ $TOTAL_PHYSICAL_MEM -lt 1000000 ]
|
||||
if [ "$TOTAL_PHYSICAL_MEM" -lt 1000000 ]
|
||||
then
|
||||
tools/editconf.py /etc/php/$PHP_VER/fpm/pool.d/www.conf -c ';' \
|
||||
tools/editconf.py /etc/php/"$PHP_VER"/fpm/pool.d/www.conf -c ';' \
|
||||
pm=ondemand \
|
||||
pm.max_children=8 \
|
||||
pm.start_servers=2 \
|
||||
pm.min_spare_servers=1 \
|
||||
pm.max_spare_servers=3
|
||||
elif [ $TOTAL_PHYSICAL_MEM -lt 2000000 ]
|
||||
elif [ "$TOTAL_PHYSICAL_MEM" -lt 2000000 ]
|
||||
then
|
||||
tools/editconf.py /etc/php/$PHP_VER/fpm/pool.d/www.conf -c ';' \
|
||||
tools/editconf.py /etc/php/"$PHP_VER"/fpm/pool.d/www.conf -c ';' \
|
||||
pm=ondemand \
|
||||
pm.max_children=16 \
|
||||
pm.start_servers=4 \
|
||||
pm.min_spare_servers=1 \
|
||||
pm.max_spare_servers=6
|
||||
elif [ $TOTAL_PHYSICAL_MEM -lt 3000000 ]
|
||||
elif [ "$TOTAL_PHYSICAL_MEM" -lt 3000000 ]
|
||||
then
|
||||
tools/editconf.py /etc/php/$PHP_VER/fpm/pool.d/www.conf -c ';' \
|
||||
tools/editconf.py /etc/php/"$PHP_VER"/fpm/pool.d/www.conf -c ';' \
|
||||
pm=dynamic \
|
||||
pm.max_children=60 \
|
||||
pm.start_servers=6 \
|
||||
pm.min_spare_servers=3 \
|
||||
pm.max_spare_servers=9
|
||||
else
|
||||
tools/editconf.py /etc/php/$PHP_VER/fpm/pool.d/www.conf -c ';' \
|
||||
tools/editconf.py /etc/php/"$PHP_VER"/fpm/pool.d/www.conf -c ';' \
|
||||
pm=dynamic \
|
||||
pm.max_children=120 \
|
||||
pm.start_servers=12 \
|
||||
@@ -124,7 +124,7 @@ chmod a+r /var/lib/mailinabox/mozilla-autoconfig.xml
|
||||
|
||||
# Create a generic mta-sts.txt file which is exposed via the
|
||||
# nginx configuration at /.well-known/mta-sts.txt
|
||||
# more documentation is available on:
|
||||
# more documentation is available on:
|
||||
# https://www.uriports.com/blog/mta-sts-explained/
|
||||
# default mode is "enforce". In /etc/mailinabox.conf change
|
||||
# "MTA_STS_MODE=testing" which means "Messages will be delivered
|
||||
@@ -138,16 +138,16 @@ cat conf/mta-sts.txt \
|
||||
chmod a+r /var/lib/mailinabox/mta-sts.txt
|
||||
|
||||
# make a default homepage
|
||||
if [ -d $STORAGE_ROOT/www/static ]; then mv $STORAGE_ROOT/www/static $STORAGE_ROOT/www/default; fi # migration #NODOC
|
||||
mkdir -p $STORAGE_ROOT/www/default
|
||||
if [ ! -f $STORAGE_ROOT/www/default/index.html ]; then
|
||||
cp conf/www_default.html $STORAGE_ROOT/www/default/index.html
|
||||
if [ -d "$STORAGE_ROOT/www/static" ]; then mv "$STORAGE_ROOT/www/static" "$STORAGE_ROOT/www/default"; fi # migration #NODOC
|
||||
mkdir -p "$STORAGE_ROOT/www/default"
|
||||
if [ ! -f "$STORAGE_ROOT/www/default/index.html" ]; then
|
||||
cp conf/www_default.html "$STORAGE_ROOT/www/default/index.html"
|
||||
fi
|
||||
chown -R $STORAGE_USER $STORAGE_ROOT/www
|
||||
chown -R "$STORAGE_USER" "$STORAGE_ROOT/www"
|
||||
|
||||
# Start services.
|
||||
restart_service nginx
|
||||
restart_service php$PHP_VER-fpm
|
||||
restart_service php"$PHP_VER"-fpm
|
||||
|
||||
# Open ports.
|
||||
ufw_allow http
|
||||
|
||||
29
setup/webmail.sh
Executable file → Normal file
29
setup/webmail.sh
Executable file → Normal file
@@ -22,8 +22,8 @@ source /etc/mailinabox.conf # load global vars
|
||||
echo "Installing Roundcube (webmail)..."
|
||||
apt_install \
|
||||
dbconfig-common \
|
||||
php${PHP_VER}-cli php${PHP_VER}-sqlite3 php${PHP_VER}-intl php${PHP_VER}-common php${PHP_VER}-curl php${PHP_VER}-imap \
|
||||
php${PHP_VER}-gd php${PHP_VER}-pspell php${PHP_VER}-mbstring libjs-jquery libjs-jquery-mousewheel libmagic1 \
|
||||
php"${PHP_VER}"-cli php"${PHP_VER}"-sqlite3 php"${PHP_VER}"-intl php"${PHP_VER}"-common php"${PHP_VER}"-curl php"${PHP_VER}"-imap \
|
||||
php"${PHP_VER}"-gd php"${PHP_VER}"-pspell php"${PHP_VER}"-mbstring libjs-jquery libjs-jquery-mousewheel libmagic1 \
|
||||
sqlite3
|
||||
|
||||
# Install Roundcube from source if it is not already present or if it is out of date.
|
||||
@@ -145,6 +145,7 @@ cat > $RCM_CONFIG <<EOF;
|
||||
\$config['session_path'] = '/mail/';
|
||||
/* prevent CSRF, requires php 7.3+ */
|
||||
\$config['session_samesite'] = 'Strict';
|
||||
\$config['quota_zero_as_unlimited'] = true;
|
||||
?>
|
||||
EOF
|
||||
|
||||
@@ -170,8 +171,8 @@ cat > ${RCM_PLUGIN_DIR}/carddav/config.inc.php <<EOF;
|
||||
EOF
|
||||
|
||||
# Create writable directories.
|
||||
mkdir -p /var/log/roundcubemail /var/tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
|
||||
chown -R www-data:www-data /var/log/roundcubemail /var/tmp/roundcubemail $STORAGE_ROOT/mail/roundcube
|
||||
mkdir -p /var/log/roundcubemail /var/tmp/roundcubemail "$STORAGE_ROOT/mail/roundcube"
|
||||
chown -R www-data:www-data /var/log/roundcubemail /var/tmp/roundcubemail "$STORAGE_ROOT/mail/roundcube"
|
||||
|
||||
# Ensure the log file monitored by fail2ban exists, or else fail2ban can't start.
|
||||
sudo -u www-data touch /var/log/roundcubemail/errors.log
|
||||
@@ -194,10 +195,10 @@ usermod -a -G dovecot www-data
|
||||
|
||||
# set permissions so that PHP can use users.sqlite
|
||||
# could use dovecot instead of www-data, but not sure it matters
|
||||
chown root:www-data $STORAGE_ROOT/mail
|
||||
chmod 775 $STORAGE_ROOT/mail
|
||||
chown root:www-data $STORAGE_ROOT/mail/users.sqlite
|
||||
chmod 664 $STORAGE_ROOT/mail/users.sqlite
|
||||
chown root:www-data "$STORAGE_ROOT/mail"
|
||||
chmod 775 "$STORAGE_ROOT/mail"
|
||||
chown root:www-data "$STORAGE_ROOT/mail/users.sqlite"
|
||||
chmod 664 "$STORAGE_ROOT/mail/users.sqlite"
|
||||
|
||||
# Fix Carddav permissions:
|
||||
chown -f -R root:www-data ${RCM_PLUGIN_DIR}/carddav
|
||||
@@ -205,9 +206,9 @@ chown -f -R root:www-data ${RCM_PLUGIN_DIR}/carddav
|
||||
chmod -R 774 ${RCM_PLUGIN_DIR}/carddav
|
||||
|
||||
# Run Roundcube database migration script (database is created if it does not exist)
|
||||
php$PHP_VER ${RCM_DIR}/bin/updatedb.sh --dir ${RCM_DIR}/SQL --package roundcube
|
||||
chown www-data:www-data $STORAGE_ROOT/mail/roundcube/roundcube.sqlite
|
||||
chmod 664 $STORAGE_ROOT/mail/roundcube/roundcube.sqlite
|
||||
php"$PHP_VER" ${RCM_DIR}/bin/updatedb.sh --dir ${RCM_DIR}/SQL --package roundcube
|
||||
chown www-data:www-data "$STORAGE_ROOT/mail/roundcube/roundcube.sqlite"
|
||||
chmod 664 "$STORAGE_ROOT/mail/roundcube/roundcube.sqlite"
|
||||
|
||||
# Patch the Roundcube code to eliminate an issue that causes postfix to reject our sqlite
|
||||
# user database (see https://github.com/mail-in-a-box/mailinabox/issues/2185)
|
||||
@@ -217,8 +218,8 @@ sed -i.miabold 's/^[^#]\+.\+PRAGMA journal_mode = WAL.\+$/#&/' \
|
||||
# Because Roundcube wants to set the PRAGMA we just deleted from the source, we apply it here
|
||||
# to the roundcube database (see https://github.com/roundcube/roundcubemail/issues/8035)
|
||||
# Database should exist, created by migration script
|
||||
hide_output sqlite3 $STORAGE_ROOT/mail/roundcube/roundcube.sqlite 'PRAGMA journal_mode=WAL;'
|
||||
hide_output sqlite3 "$STORAGE_ROOT/mail/roundcube/roundcube.sqlite" 'PRAGMA journal_mode=WAL;'
|
||||
|
||||
# Enable PHP modules.
|
||||
phpenmod -v $PHP_VER imap
|
||||
restart_service php$PHP_VER-fpm
|
||||
phpenmod -v "$PHP_VER" imap
|
||||
restart_service php"$PHP_VER"-fpm
|
||||
|
||||
@@ -17,9 +17,9 @@ source /etc/mailinabox.conf # load global vars
|
||||
|
||||
echo "Installing Z-Push (Exchange/ActiveSync server)..."
|
||||
apt_install \
|
||||
php${PHP_VER}-soap php${PHP_VER}-imap libawl-php php$PHP_VER-xml
|
||||
php"${PHP_VER}"-soap php"${PHP_VER}"-imap libawl-php php"$PHP_VER"-xml
|
||||
|
||||
phpenmod -v $PHP_VER imap
|
||||
phpenmod -v "$PHP_VER" imap
|
||||
|
||||
# Copy Z-Push into place.
|
||||
VERSION=2.7.1
|
||||
@@ -44,10 +44,10 @@ if [ $needs_update == 1 ]; then
|
||||
# Create admin and top scripts with PHP_VER
|
||||
rm -f /usr/sbin/z-push-{admin,top}
|
||||
echo '#!/bin/bash' > /usr/sbin/z-push-admin
|
||||
echo php$PHP_VER /usr/local/lib/z-push/z-push-admin.php '"$@"' >> /usr/sbin/z-push-admin
|
||||
echo php"$PHP_VER" /usr/local/lib/z-push/z-push-admin.php '"$@"' >> /usr/sbin/z-push-admin
|
||||
chmod 755 /usr/sbin/z-push-admin
|
||||
echo '#!/bin/bash' > /usr/sbin/z-push-top
|
||||
echo php$PHP_VER /usr/local/lib/z-push/z-push-top.php '"$@"' >> /usr/sbin/z-push-top
|
||||
echo php"$PHP_VER" /usr/local/lib/z-push/z-push-top.php '"$@"' >> /usr/sbin/z-push-top
|
||||
chmod 755 /usr/sbin/z-push-top
|
||||
|
||||
echo $VERSION > /usr/local/lib/z-push/version
|
||||
@@ -108,8 +108,8 @@ EOF
|
||||
|
||||
# Restart service.
|
||||
|
||||
restart_service php$PHP_VER-fpm
|
||||
restart_service php"$PHP_VER"-fpm
|
||||
|
||||
# Fix states after upgrade
|
||||
|
||||
hide_output php$PHP_VER /usr/local/lib/z-push/z-push-admin.php -a fixstates
|
||||
hide_output php"$PHP_VER" /usr/local/lib/z-push/z-push-admin.php -a fixstates
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#!/bin/bash
|
||||
# Use this script to make an archive of the contents of all
|
||||
# of the configuration files we edit with editconf.py.
|
||||
for fn in `grep -hr editconf.py setup | sed "s/tools\/editconf.py //" | sed "s/ .*//" | sort | uniq`; do
|
||||
for fn in $(grep -hr editconf.py setup | sed "s/tools\/editconf.py //" | sed "s/ .*//" | sort | uniq); do
|
||||
echo ======================================================================
|
||||
echo $fn
|
||||
echo "$fn"
|
||||
echo ======================================================================
|
||||
cat $fn
|
||||
cat "$fn"
|
||||
done
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@ POSTDATA=dummy
|
||||
if [ "$1" == "--force" ]; then
|
||||
POSTDATA=force=1
|
||||
fi
|
||||
curl -s -d $POSTDATA --user $(</var/lib/mailinabox/api.key): http://127.0.0.1:10222/dns/update
|
||||
curl -s -d $POSTDATA --user "$(</var/lib/mailinabox/api.key):" http://127.0.0.1:10222/dns/update
|
||||
|
||||
@@ -14,13 +14,13 @@ if [ -z "$1" ]; then
|
||||
echo
|
||||
echo "Available backups:"
|
||||
echo
|
||||
find $STORAGE_ROOT/owncloud-backup/* -maxdepth 0 -type d
|
||||
find "$STORAGE_ROOT/owncloud-backup/"* -maxdepth 0 -type d
|
||||
echo
|
||||
echo "Supply the directory that was created during the last installation as the only commandline argument"
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ ! -f $1/config.php ]; then
|
||||
if [ ! -f "$1/config.php" ]; then
|
||||
echo "This isn't a valid backup location"
|
||||
exit 1
|
||||
fi
|
||||
@@ -36,14 +36,14 @@ cp -r "$1/owncloud-install" /usr/local/lib/owncloud
|
||||
# restore access rights
|
||||
chmod 750 /usr/local/lib/owncloud/{apps,config}
|
||||
|
||||
cp "$1/owncloud.db" $STORAGE_ROOT/owncloud/
|
||||
cp "$1/config.php" $STORAGE_ROOT/owncloud/
|
||||
cp "$1/owncloud.db" "$STORAGE_ROOT/owncloud/"
|
||||
cp "$1/config.php" "$STORAGE_ROOT/owncloud/"
|
||||
|
||||
ln -sf $STORAGE_ROOT/owncloud/config.php /usr/local/lib/owncloud/config/config.php
|
||||
chown -f -R www-data:www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
|
||||
chown www-data:www-data $STORAGE_ROOT/owncloud/config.php
|
||||
ln -sf "$STORAGE_ROOT/owncloud/config.php" /usr/local/lib/owncloud/config/config.php
|
||||
chown -f -R www-data:www-data "$STORAGE_ROOT/owncloud" /usr/local/lib/owncloud
|
||||
chown www-data:www-data "$STORAGE_ROOT/owncloud/config.php"
|
||||
|
||||
sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/occ maintenance:mode --off
|
||||
sudo -u www-data "php$PHP_VER" /usr/local/lib/owncloud/occ maintenance:mode --off
|
||||
|
||||
service php8.0-fpm start
|
||||
echo "Done"
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
source /etc/mailinabox.conf # load global vars
|
||||
|
||||
ADMIN=$(./mail.py user admins | head -n 1)
|
||||
test -z "$1" || ADMIN=$1
|
||||
test -z "$1" || ADMIN=$1
|
||||
|
||||
echo I am going to unlock admin features for $ADMIN.
|
||||
echo You can provide another user to unlock as the first argument of this script.
|
||||
echo "I am going to unlock admin features for $ADMIN."
|
||||
echo "You can provide another user to unlock as the first argument of this script."
|
||||
echo
|
||||
echo WARNING: you could break mail-in-a-box when fiddling around with Nextcloud\'s admin interface
|
||||
echo If in doubt, press CTRL-C to cancel.
|
||||
echo
|
||||
echo Press enter to continue.
|
||||
echo "WARNING: you could break mail-in-a-box when fiddling around with Nextcloud's admin interface"
|
||||
echo "If in doubt, press CTRL-C to cancel."
|
||||
echo
|
||||
echo "Press enter to continue."
|
||||
read
|
||||
|
||||
sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/occ group:adduser admin $ADMIN && echo Done.
|
||||
sudo -u www-data "php$PHP_VER" /usr/local/lib/owncloud/occ group:adduser admin "$ADMIN" && echo "Done."
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#!/bin/bash
|
||||
curl -s -d POSTDATA --user $(</var/lib/mailinabox/api.key): http://127.0.0.1:10222/web/update
|
||||
curl -s -d POSTDATA --user "$(</var/lib/mailinabox/api.key):" http://127.0.0.1:10222/web/update
|
||||
|
||||
Reference in New Issue
Block a user