mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2026-03-28 19:47:23 +01:00
Compare commits
78 Commits
v60
...
0c82b0f6a9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c82b0f6a9 | ||
|
|
a5ebd9c6a6 | ||
|
|
558d8be901 | ||
|
|
8c040ce7f4 | ||
|
|
ee585fa80a | ||
|
|
afe3c2f139 | ||
|
|
dfd542f362 | ||
|
|
6c89bb41db | ||
|
|
8e53c72ecb | ||
|
|
f04af59ffd | ||
|
|
6303c01bc1 | ||
|
|
62c742bbf7 | ||
|
|
1bbb7e2b4f | ||
|
|
180815d20f | ||
|
|
16844f2a8d | ||
|
|
fe953c050e | ||
|
|
4175448b36 | ||
|
|
58be3194c0 | ||
|
|
7655a7688c | ||
|
|
8e4e9add78 | ||
|
|
fa8c7ddef5 | ||
|
|
6d6ce25e03 | ||
|
|
371f5bc1b2 | ||
|
|
0314554207 | ||
|
|
46d55f7866 | ||
|
|
2bbc317873 | ||
|
|
28f929dc13 | ||
|
|
e419b62034 | ||
|
|
a966913963 | ||
|
|
08defb12be | ||
|
|
7be687e601 | ||
|
|
62efe985f1 | ||
|
|
df44056bae | ||
|
|
3148c621d2 | ||
|
|
81866de229 | ||
|
|
674ce92e92 | ||
|
|
c034b0f789 | ||
|
|
cd45d08409 | ||
|
|
98628622c7 | ||
|
|
8b19d15735 | ||
|
|
93380b243f | ||
|
|
fb0a3b0489 | ||
|
|
3bc9d07aeb | ||
|
|
51ed030917 | ||
|
|
e828d63a85 | ||
|
|
0ee0784bde | ||
|
|
6d43d24552 | ||
|
|
963fb9f2e6 | ||
|
|
c9584148a0 | ||
|
|
9a33f9c5ff | ||
|
|
95530affbf | ||
|
|
f72be0be7c | ||
|
|
8aa98b25b5 | ||
|
|
3c15081673 | ||
|
|
01d8e9f3b4 | ||
|
|
88260bb610 | ||
|
|
6f94412204 | ||
|
|
c77d1697a7 | ||
|
|
31bbef3401 | ||
|
|
7af713592a | ||
|
|
4408cb1fba | ||
|
|
5e3e4a2161 | ||
|
|
61d1ea1ea7 | ||
|
|
b3743a31e9 | ||
|
|
26709a3c1d | ||
|
|
20ec6c2080 | ||
|
|
7a79153afe | ||
|
|
a2565227f2 | ||
|
|
02b34ce699 | ||
|
|
820a39b865 | ||
|
|
57047d96e9 | ||
|
|
1587248762 | ||
|
|
0fc5105da5 | ||
|
|
c29593b5ef | ||
|
|
3314c4f7de | ||
|
|
1f60236985 | ||
|
|
32c68874c5 | ||
|
|
286a4bd9e7 |
87
CHANGELOG.md
87
CHANGELOG.md
@@ -1,6 +1,91 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
Version 66 (December 17, 2023)
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
* Some users reported an error installing Mail-in-a-Box related to the virtualenv command. This is hopefully fixed.
|
||||||
|
* Roundcube is updated to 1.6.5 fixing a security vulnerability.
|
||||||
|
* For Mail-in-a-Box developers, a new setup variable is added to pull the source code from a different repository.
|
||||||
|
|
||||||
|
Version 65 (October 27, 2023)
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
* Roundcube updated to 1.6.4 fixing a security vulnerability.
|
||||||
|
* zpush.sh updated to version 2.7.1.
|
||||||
|
* Fixed a typo in the control panel.
|
||||||
|
|
||||||
|
Version 64 (September 2, 2023)
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
* Fixed broken installation when upgrading from Mail-in-a-Box version 56 (Nextcloud 22) and earlier because of an upstream packaging issue.
|
||||||
|
* Fixed backups to work with the latest duplicity package which was not backwards compatible.
|
||||||
|
* Fixed setting B2 as a backup target with a slash in the application key.
|
||||||
|
* Turned off OpenDMARC diagnostic reports sent in response to incoming mail.
|
||||||
|
* Fixed some crashes when using an unrelased version of Mail-in-a-Box.
|
||||||
|
* Added z-push administration scripts.
|
||||||
|
|
||||||
|
Version 63 (July 27, 2023)
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
* Nextcloud updated to 25.0.7.
|
||||||
|
|
||||||
|
Version 62 (May 20, 2023)
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Package updates:
|
||||||
|
|
||||||
|
* Nextcloud updated to 23.0.12 (and its apps also updated).
|
||||||
|
* Roundcube updated to 1.6.1.
|
||||||
|
* Z-Push to 2.7.0, which has compatibility for Ubuntu 22.04, so it works again.
|
||||||
|
|
||||||
|
Mail:
|
||||||
|
|
||||||
|
* Roundcube's password change page is now working again.
|
||||||
|
|
||||||
|
Control panel:
|
||||||
|
|
||||||
|
* Allow setting the backup location's S3 region name for non-AWS S3-compatible backup hosts.
|
||||||
|
* Control panel pages can be opened in a new tab/window and bookmarked and browser history navigation now works.
|
||||||
|
* Add a Copy button to put the rsync backup public key on clipboard.
|
||||||
|
* Allow secondary DNS xfr: items added in the control panel to be hostnames too.
|
||||||
|
* Fixed issue where sshkeygen fails when IPv6 is disabled.
|
||||||
|
* Fixed issue opening munin reports.
|
||||||
|
* Fixed report formatting in status emails sent to the administrator.
|
||||||
|
|
||||||
|
Version 61.1 (January 28, 2023)
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
* Fixed rsync backups not working with the default port.
|
||||||
|
* Reverted "Improve error messages in the management tools when external command-line tools are run." because of the possibility of user secrets being included in error messages.
|
||||||
|
* Fix for TLS certificate SHA fingerprint not being displayed during setup.
|
||||||
|
|
||||||
|
Version 61 (January 21, 2023)
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
System:
|
||||||
|
|
||||||
|
* fail2ban didn't start after setup.
|
||||||
|
|
||||||
|
Mail:
|
||||||
|
|
||||||
|
* Disable Roundcube password plugin since it was corrupting the user database.
|
||||||
|
|
||||||
|
Control panel:
|
||||||
|
|
||||||
|
* Fix changing existing backup settings when the rsync type is used.
|
||||||
|
* Allow setting a custom port for rsync backups.
|
||||||
|
* Fixes to DNS lookups during status checks when there are timeouts, enforce timeouts better.
|
||||||
|
* A new check is added to ensure fail2ban is running.
|
||||||
|
* Fixed a color.
|
||||||
|
* Improve error messages in the management tools when external command-line tools are run.
|
||||||
|
|
||||||
|
Version 60.1 (October 30, 2022)
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
* A setup issue where the DNS server nsd isn't running at the end of setup is (hopefully) fixed.
|
||||||
|
* Nextcloud is updated to 23.0.10 (contacts to 4.2.2, calendar to 3.5.1).
|
||||||
|
|
||||||
Version 60 (October 11, 2022)
|
Version 60 (October 11, 2022)
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
@@ -17,7 +102,7 @@ No major features of Mail-in-a-Box have changed in this release, although some m
|
|||||||
With the newer version of Ubuntu the following software packages we use are updated:
|
With the newer version of Ubuntu the following software packages we use are updated:
|
||||||
|
|
||||||
* dovecot is upgraded to 2.3.16, postfix to 3.6.4, opendmark to 1.4 (which adds ARC-Authentication-Results headers), and spampd to 2.53 (alleviating a mail delivery rate limiting bug).
|
* dovecot is upgraded to 2.3.16, postfix to 3.6.4, opendmark to 1.4 (which adds ARC-Authentication-Results headers), and spampd to 2.53 (alleviating a mail delivery rate limiting bug).
|
||||||
* Nextcloud is upgraded to 23.0.4.
|
* Nextcloud is upgraded to 23.0.4 (contacts to 4.2.0, calendar to 3.5.0).
|
||||||
* Roundcube is upgraded to 1.6.0.
|
* Roundcube is upgraded to 1.6.0.
|
||||||
* certbot is upgraded to 1.21 (via the Ubuntu repository instead of a PPA).
|
* certbot is upgraded to 1.21 (via the Ubuntu repository instead of a PPA).
|
||||||
* fail2ban is upgraded to 0.11.2.
|
* fail2ban is upgraded to 0.11.2.
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ Clone this repository and checkout the tag corresponding to the most recent rele
|
|||||||
|
|
||||||
$ git clone https://github.com/mail-in-a-box/mailinabox
|
$ git clone https://github.com/mail-in-a-box/mailinabox
|
||||||
$ cd mailinabox
|
$ cd mailinabox
|
||||||
$ git checkout v60
|
$ git checkout v66
|
||||||
|
|
||||||
Begin the installation.
|
Begin the installation.
|
||||||
|
|
||||||
|
|||||||
@@ -73,4 +73,9 @@
|
|||||||
rewrite ^/.well-known/carddav /cloud/remote.php/carddav/ redirect;
|
rewrite ^/.well-known/carddav /cloud/remote.php/carddav/ redirect;
|
||||||
rewrite ^/.well-known/caldav /cloud/remote.php/caldav/ redirect;
|
rewrite ^/.well-known/caldav /cloud/remote.php/caldav/ redirect;
|
||||||
|
|
||||||
|
# This addresses those service discovery issues mentioned in:
|
||||||
|
# https://docs.nextcloud.com/server/23/admin_manual/issues/general_troubleshooting.html#service-discovery
|
||||||
|
rewrite ^/.well-known/webfinger /cloud/index.php/.well-known/webfinger redirect;
|
||||||
|
rewrite ^/.well-known/nodeinfo /cloud/index.php/.well-known/nodeinfo redirect;
|
||||||
|
|
||||||
# ADDITIONAL DIRECTIVES HERE
|
# ADDITIONAL DIRECTIVES HERE
|
||||||
|
|||||||
@@ -57,10 +57,11 @@ def backup_status(env):
|
|||||||
"/usr/bin/duplicity",
|
"/usr/bin/duplicity",
|
||||||
"collection-status",
|
"collection-status",
|
||||||
"--archive-dir", backup_cache_dir,
|
"--archive-dir", backup_cache_dir,
|
||||||
"--gpg-options", "--cipher-algo=AES256",
|
"--gpg-options", "'--cipher-algo=AES256'",
|
||||||
"--log-fd", "1",
|
"--log-fd", "1",
|
||||||
get_duplicity_target_url(config),
|
] + get_duplicity_additional_args(env) + [
|
||||||
] + get_duplicity_additional_args(env),
|
get_duplicity_target_url(config)
|
||||||
|
],
|
||||||
get_duplicity_env_vars(env),
|
get_duplicity_env_vars(env),
|
||||||
trap=True)
|
trap=True)
|
||||||
if code != 0:
|
if code != 0:
|
||||||
@@ -202,7 +203,9 @@ def get_duplicity_target_url(config):
|
|||||||
# the target URL must be the bucket name. The hostname is passed
|
# the target URL must be the bucket name. The hostname is passed
|
||||||
# via get_duplicity_additional_args. Move the first part of the
|
# via get_duplicity_additional_args. Move the first part of the
|
||||||
# path (the bucket name) into the hostname URL component, and leave
|
# path (the bucket name) into the hostname URL component, and leave
|
||||||
# the rest for the path.
|
# the rest for the path. (The S3 region name is also stored in the
|
||||||
|
# hostname part of the URL, in the username portion, which we also
|
||||||
|
# have to drop here).
|
||||||
target[1], target[2] = target[2].lstrip('/').split('/', 1)
|
target[1], target[2] = target[2].lstrip('/').split('/', 1)
|
||||||
|
|
||||||
target = urlunsplit(target)
|
target = urlunsplit(target)
|
||||||
@@ -213,16 +216,32 @@ def get_duplicity_additional_args(env):
|
|||||||
config = get_backup_config(env)
|
config = get_backup_config(env)
|
||||||
|
|
||||||
if get_target_type(config) == 'rsync':
|
if get_target_type(config) == 'rsync':
|
||||||
|
# Extract a port number for the ssh transport. Duplicity accepts the
|
||||||
|
# optional port number syntax in the target, but it doesn't appear to act
|
||||||
|
# on it, so we set the ssh port explicitly via the duplicity options.
|
||||||
|
from urllib.parse import urlsplit
|
||||||
|
try:
|
||||||
|
port = urlsplit(config["target"]).port
|
||||||
|
except ValueError:
|
||||||
|
port = 22
|
||||||
|
if port is None:
|
||||||
|
port = 22
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"--ssh-options= -i /root/.ssh/id_rsa_miab",
|
f"--ssh-options='-i /root/.ssh/id_rsa_miab -p {port}'",
|
||||||
"--rsync-options= -e \"/usr/bin/ssh -oStrictHostKeyChecking=no -oBatchMode=yes -p 22 -i /root/.ssh/id_rsa_miab\"",
|
f"--rsync-options='-e \"/usr/bin/ssh -oStrictHostKeyChecking=no -oBatchMode=yes -p {port} -i /root/.ssh/id_rsa_miab\"'",
|
||||||
]
|
]
|
||||||
elif get_target_type(config) == 's3':
|
elif get_target_type(config) == 's3':
|
||||||
# See note about hostname in get_duplicity_target_url.
|
# See note about hostname in get_duplicity_target_url.
|
||||||
|
# The region name, which is required by some non-AWS endpoints,
|
||||||
|
# is saved inside the username portion of the URL.
|
||||||
from urllib.parse import urlsplit, urlunsplit
|
from urllib.parse import urlsplit, urlunsplit
|
||||||
target = urlsplit(config["target"])
|
target = urlsplit(config["target"])
|
||||||
endpoint_url = urlunsplit(("https", target.netloc, '', '', ''))
|
endpoint_url = urlunsplit(("https", target.hostname, '', '', ''))
|
||||||
return ["--s3-endpoint-url", endpoint_url]
|
args = ["--s3-endpoint-url", endpoint_url]
|
||||||
|
if target.username: # region name is stuffed here
|
||||||
|
args += ["--s3-region-name", target.username]
|
||||||
|
return args
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -303,11 +322,12 @@ def perform_backup(full_backup):
|
|||||||
"--archive-dir", backup_cache_dir,
|
"--archive-dir", backup_cache_dir,
|
||||||
"--exclude", backup_root,
|
"--exclude", backup_root,
|
||||||
"--volsize", "250",
|
"--volsize", "250",
|
||||||
"--gpg-options", "--cipher-algo=AES256",
|
"--gpg-options", "'--cipher-algo=AES256'",
|
||||||
|
"--allow-source-mismatch"
|
||||||
|
] + get_duplicity_additional_args(env) + [
|
||||||
env["STORAGE_ROOT"],
|
env["STORAGE_ROOT"],
|
||||||
get_duplicity_target_url(config),
|
get_duplicity_target_url(config),
|
||||||
"--allow-source-mismatch"
|
],
|
||||||
] + get_duplicity_additional_args(env),
|
|
||||||
get_duplicity_env_vars(env))
|
get_duplicity_env_vars(env))
|
||||||
finally:
|
finally:
|
||||||
# Start services again.
|
# Start services again.
|
||||||
@@ -325,8 +345,9 @@ def perform_backup(full_backup):
|
|||||||
"--verbosity", "error",
|
"--verbosity", "error",
|
||||||
"--archive-dir", backup_cache_dir,
|
"--archive-dir", backup_cache_dir,
|
||||||
"--force",
|
"--force",
|
||||||
|
] + get_duplicity_additional_args(env) + [
|
||||||
get_duplicity_target_url(config)
|
get_duplicity_target_url(config)
|
||||||
] + get_duplicity_additional_args(env),
|
],
|
||||||
get_duplicity_env_vars(env))
|
get_duplicity_env_vars(env))
|
||||||
|
|
||||||
# From duplicity's manual:
|
# From duplicity's manual:
|
||||||
@@ -340,8 +361,9 @@ def perform_backup(full_backup):
|
|||||||
"--verbosity", "error",
|
"--verbosity", "error",
|
||||||
"--archive-dir", backup_cache_dir,
|
"--archive-dir", backup_cache_dir,
|
||||||
"--force",
|
"--force",
|
||||||
|
] + get_duplicity_additional_args(env) + [
|
||||||
get_duplicity_target_url(config)
|
get_duplicity_target_url(config)
|
||||||
] + get_duplicity_additional_args(env),
|
],
|
||||||
get_duplicity_env_vars(env))
|
get_duplicity_env_vars(env))
|
||||||
|
|
||||||
# Change ownership of backups to the user-data user, so that the after-bcakup
|
# Change ownership of backups to the user-data user, so that the after-bcakup
|
||||||
@@ -378,9 +400,10 @@ def run_duplicity_verification():
|
|||||||
"--compare-data",
|
"--compare-data",
|
||||||
"--archive-dir", backup_cache_dir,
|
"--archive-dir", backup_cache_dir,
|
||||||
"--exclude", backup_root,
|
"--exclude", backup_root,
|
||||||
|
] + get_duplicity_additional_args(env) + [
|
||||||
get_duplicity_target_url(config),
|
get_duplicity_target_url(config),
|
||||||
env["STORAGE_ROOT"],
|
env["STORAGE_ROOT"],
|
||||||
] + get_duplicity_additional_args(env), get_duplicity_env_vars(env))
|
], get_duplicity_env_vars(env))
|
||||||
|
|
||||||
def run_duplicity_restore(args):
|
def run_duplicity_restore(args):
|
||||||
env = load_environment()
|
env = load_environment()
|
||||||
@@ -390,9 +413,23 @@ def run_duplicity_restore(args):
|
|||||||
"/usr/bin/duplicity",
|
"/usr/bin/duplicity",
|
||||||
"restore",
|
"restore",
|
||||||
"--archive-dir", backup_cache_dir,
|
"--archive-dir", backup_cache_dir,
|
||||||
get_duplicity_target_url(config),
|
] + get_duplicity_additional_args(env) + [
|
||||||
] + get_duplicity_additional_args(env) + args,
|
get_duplicity_target_url(config)
|
||||||
get_duplicity_env_vars(env))
|
] + args,
|
||||||
|
get_duplicity_env_vars(env))
|
||||||
|
|
||||||
|
def print_duplicity_command():
|
||||||
|
import shlex
|
||||||
|
env = load_environment()
|
||||||
|
config = get_backup_config(env)
|
||||||
|
backup_cache_dir = os.path.join(env["STORAGE_ROOT"], 'backup', 'cache')
|
||||||
|
for k, v in get_duplicity_env_vars(env).items():
|
||||||
|
print(f"export {k}={shlex.quote(v)}")
|
||||||
|
print("duplicity", "{command}", shlex.join([
|
||||||
|
"--archive-dir", backup_cache_dir,
|
||||||
|
] + get_duplicity_additional_args(env) + [
|
||||||
|
get_duplicity_target_url(config)
|
||||||
|
]))
|
||||||
|
|
||||||
def list_target_files(config):
|
def list_target_files(config):
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
@@ -408,6 +445,16 @@ def list_target_files(config):
|
|||||||
rsync_fn_size_re = re.compile(r'.* ([^ ]*) [^ ]* [^ ]* (.*)')
|
rsync_fn_size_re = re.compile(r'.* ([^ ]*) [^ ]* [^ ]* (.*)')
|
||||||
rsync_target = '{host}:{path}'
|
rsync_target = '{host}:{path}'
|
||||||
|
|
||||||
|
# Strip off any trailing port specifier because it's not valid in rsync's
|
||||||
|
# DEST syntax. Explicitly set the port number for the ssh transport.
|
||||||
|
user_host, *_ = target.netloc.rsplit(':', 1)
|
||||||
|
try:
|
||||||
|
port = target.port
|
||||||
|
except ValueError:
|
||||||
|
port = 22
|
||||||
|
if port is None:
|
||||||
|
port = 22
|
||||||
|
|
||||||
target_path = target.path
|
target_path = target.path
|
||||||
if not target_path.endswith('/'):
|
if not target_path.endswith('/'):
|
||||||
target_path = target_path + '/'
|
target_path = target_path + '/'
|
||||||
@@ -416,11 +463,11 @@ def list_target_files(config):
|
|||||||
|
|
||||||
rsync_command = [ 'rsync',
|
rsync_command = [ 'rsync',
|
||||||
'-e',
|
'-e',
|
||||||
'/usr/bin/ssh -i /root/.ssh/id_rsa_miab -oStrictHostKeyChecking=no -oBatchMode=yes',
|
f'/usr/bin/ssh -i /root/.ssh/id_rsa_miab -oStrictHostKeyChecking=no -oBatchMode=yes -p {port}',
|
||||||
'--list-only',
|
'--list-only',
|
||||||
'-r',
|
'-r',
|
||||||
rsync_target.format(
|
rsync_target.format(
|
||||||
host=target.netloc,
|
host=user_host,
|
||||||
path=target_path)
|
path=target_path)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -481,7 +528,7 @@ def list_target_files(config):
|
|||||||
|
|
||||||
# Extract information from target
|
# Extract information from target
|
||||||
b2_application_keyid = target.netloc[:target.netloc.index(':')]
|
b2_application_keyid = target.netloc[:target.netloc.index(':')]
|
||||||
b2_application_key = target.netloc[target.netloc.index(':')+1:target.netloc.index('@')]
|
b2_application_key = urllib.parse.unquote(target.netloc[target.netloc.index(':')+1:target.netloc.index('@')])
|
||||||
b2_bucket = target.netloc[target.netloc.index('@')+1:]
|
b2_bucket = target.netloc[target.netloc.index('@')+1:]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -531,7 +578,8 @@ def get_backup_config(env, for_save=False, for_ui=False):
|
|||||||
|
|
||||||
# Merge in anything written to custom.yaml.
|
# Merge in anything written to custom.yaml.
|
||||||
try:
|
try:
|
||||||
custom_config = rtyaml.load(open(os.path.join(backup_root, 'custom.yaml')))
|
with open(os.path.join(backup_root, 'custom.yaml'), 'r') as f:
|
||||||
|
custom_config = rtyaml.load(f)
|
||||||
if not isinstance(custom_config, dict): raise ValueError() # caught below
|
if not isinstance(custom_config, dict): raise ValueError() # caught below
|
||||||
config.update(custom_config)
|
config.update(custom_config)
|
||||||
except:
|
except:
|
||||||
@@ -556,7 +604,8 @@ def get_backup_config(env, for_save=False, for_ui=False):
|
|||||||
config["target"] = "file://" + config["file_target_directory"]
|
config["target"] = "file://" + config["file_target_directory"]
|
||||||
ssh_pub_key = os.path.join('/root', '.ssh', 'id_rsa_miab.pub')
|
ssh_pub_key = os.path.join('/root', '.ssh', 'id_rsa_miab.pub')
|
||||||
if os.path.exists(ssh_pub_key):
|
if os.path.exists(ssh_pub_key):
|
||||||
config["ssh_pub_key"] = open(ssh_pub_key, 'r').read()
|
with open(ssh_pub_key, 'r') as f:
|
||||||
|
config["ssh_pub_key"] = f.read()
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@@ -588,6 +637,9 @@ if __name__ == "__main__":
|
|||||||
# to duplicity. The restore path should be specified.
|
# to duplicity. The restore path should be specified.
|
||||||
run_duplicity_restore(sys.argv[2:])
|
run_duplicity_restore(sys.argv[2:])
|
||||||
|
|
||||||
|
elif sys.argv[-1] == "--duplicity-command":
|
||||||
|
print_duplicity_command()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Perform a backup. Add --full to force a full backup rather than
|
# Perform a backup. Add --full to force a full backup rather than
|
||||||
# possibly performing an incremental backup.
|
# possibly performing an incremental backup.
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ def read_password():
|
|||||||
return first
|
return first
|
||||||
|
|
||||||
def setup_key_auth(mgmt_uri):
|
def setup_key_auth(mgmt_uri):
|
||||||
key = open('/var/lib/mailinabox/api.key').read().strip()
|
with open('/var/lib/mailinabox/api.key', 'r') as f:
|
||||||
|
key = f.read().strip()
|
||||||
|
|
||||||
auth_handler = urllib.request.HTTPBasicAuthHandler()
|
auth_handler = urllib.request.HTTPBasicAuthHandler()
|
||||||
auth_handler.add_password(
|
auth_handler.add_password(
|
||||||
|
|||||||
@@ -709,7 +709,7 @@ def munin_cgi(filename):
|
|||||||
support infrastructure like spawn-fcgi.
|
support infrastructure like spawn-fcgi.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
COMMAND = 'su - munin --preserve-environment --shell=/bin/bash -c /usr/lib/munin/cgi/munin-cgi-graph'
|
COMMAND = 'su munin --preserve-environment --shell=/bin/bash -c /usr/lib/munin/cgi/munin-cgi-graph'
|
||||||
# su changes user, we use the munin user here
|
# su changes user, we use the munin user here
|
||||||
# --preserve-environment retains the environment, which is where Popen's `env` data is
|
# --preserve-environment retains the environment, which is where Popen's `env` data is
|
||||||
# --shell=/bin/bash ensures the shell used is bash
|
# --shell=/bin/bash ensures the shell used is bash
|
||||||
|
|||||||
@@ -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
|
# 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.
|
# 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"
|
management/mail_log.py -t week | management/email_administrator.py "Mail-in-a-Box Usage Report"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,16 @@ def do_dns_update(env, force=False):
|
|||||||
|
|
||||||
# Tell nsd to reload changed zone files.
|
# Tell nsd to reload changed zone files.
|
||||||
if len(updated_domains) > 0:
|
if len(updated_domains) > 0:
|
||||||
shell('check_call', ["/usr/sbin/nsd-control", "reload"])
|
# 'reconfig' is needed if there are added or removed zones, but
|
||||||
|
# it may not reload existing zones, so we call 'reload' too. If
|
||||||
|
# nsd isn't running, nsd-control fails, so in that case revert
|
||||||
|
# to restarting nsd to make sure it is running. Restarting nsd
|
||||||
|
# should also refresh everything.
|
||||||
|
try:
|
||||||
|
shell('check_call', ["/usr/sbin/nsd-control", "reconfig"])
|
||||||
|
shell('check_call', ["/usr/sbin/nsd-control", "reload"])
|
||||||
|
except:
|
||||||
|
shell('check_call', ["/usr/sbin/service", "nsd", "restart"])
|
||||||
|
|
||||||
# Write the OpenDKIM configuration tables for all of the mail domains.
|
# Write the OpenDKIM configuration tables for all of the mail domains.
|
||||||
from mailconfig import get_mail_domains
|
from mailconfig import get_mail_domains
|
||||||
@@ -456,7 +465,7 @@ def build_sshfp_records():
|
|||||||
pass
|
pass
|
||||||
break
|
break
|
||||||
|
|
||||||
keys = shell("check_output", ["ssh-keyscan", "-t", "rsa,dsa,ecdsa,ed25519", "-p", str(port), "localhost"])
|
keys = shell("check_output", ["ssh-keyscan", "-4", "-t", "rsa,dsa,ecdsa,ed25519", "-p", str(port), "localhost"])
|
||||||
keys = sorted(keys.split("\n"))
|
keys = sorted(keys.split("\n"))
|
||||||
|
|
||||||
for key in keys:
|
for key in keys:
|
||||||
@@ -806,7 +815,8 @@ def write_opendkim_tables(domains, env):
|
|||||||
|
|
||||||
def get_custom_dns_config(env, only_real_records=False):
|
def get_custom_dns_config(env, only_real_records=False):
|
||||||
try:
|
try:
|
||||||
custom_dns = rtyaml.load(open(os.path.join(env['STORAGE_ROOT'], 'dns/custom.yaml')))
|
with open(os.path.join(env['STORAGE_ROOT'], 'dns/custom.yaml'), 'r') as f:
|
||||||
|
custom_dns = rtyaml.load(f)
|
||||||
if not isinstance(custom_dns, dict): raise ValueError() # caught below
|
if not isinstance(custom_dns, dict): raise ValueError() # caught below
|
||||||
except:
|
except:
|
||||||
return [ ]
|
return [ ]
|
||||||
@@ -983,6 +993,7 @@ def set_custom_dns_record(qname, rtype, value, action, env):
|
|||||||
def get_secondary_dns(custom_dns, mode=None):
|
def get_secondary_dns(custom_dns, mode=None):
|
||||||
resolver = dns.resolver.get_default_resolver()
|
resolver = dns.resolver.get_default_resolver()
|
||||||
resolver.timeout = 10
|
resolver.timeout = 10
|
||||||
|
resolver.lifetime = 10
|
||||||
|
|
||||||
values = []
|
values = []
|
||||||
for qname, rtype, value in custom_dns:
|
for qname, rtype, value in custom_dns:
|
||||||
@@ -994,25 +1005,33 @@ def get_secondary_dns(custom_dns, mode=None):
|
|||||||
values.append(hostname)
|
values.append(hostname)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# This is a hostname. Before including in zone xfr lines,
|
# If the entry starts with "xfr:" only include it in the zone transfer settings.
|
||||||
# resolve to an IP address. Otherwise just return the hostname.
|
if hostname.startswith("xfr:"):
|
||||||
# It may not resolve to IPv6, so don't throw an exception if it
|
if mode != "xfr": continue
|
||||||
# doesn't.
|
hostname = hostname[4:]
|
||||||
if not hostname.startswith("xfr:"):
|
|
||||||
if mode == "xfr":
|
|
||||||
response = dns.resolver.resolve(hostname+'.', "A", raise_on_no_answer=False)
|
|
||||||
values.extend(map(str, response))
|
|
||||||
response = dns.resolver.resolve(hostname+'.', "AAAA", raise_on_no_answer=False)
|
|
||||||
values.extend(map(str, response))
|
|
||||||
continue
|
|
||||||
values.append(hostname)
|
|
||||||
|
|
||||||
# This is a zone-xfer-only IP address. Do not return if
|
# If is a hostname, before including in zone xfr lines,
|
||||||
# we're querying for NS record hostnames. Only return if
|
# resolve to an IP address.
|
||||||
# we're querying for zone xfer IP addresses - return the
|
# It may not resolve to IPv6, so don't throw an exception if it
|
||||||
# IP address.
|
# doesn't. Skip the entry if there is a DNS error.
|
||||||
elif mode == "xfr":
|
if mode == "xfr":
|
||||||
values.append(hostname[4:])
|
try:
|
||||||
|
ipaddress.ip_interface(hostname) # test if it's an IP address or CIDR notation
|
||||||
|
values.append(hostname)
|
||||||
|
except ValueError:
|
||||||
|
try:
|
||||||
|
response = dns.resolver.resolve(hostname+'.', "A", raise_on_no_answer=False)
|
||||||
|
values.extend(map(str, response))
|
||||||
|
except dns.exception.DNSException:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
response = dns.resolver.resolve(hostname+'.', "AAAA", raise_on_no_answer=False)
|
||||||
|
values.extend(map(str, response))
|
||||||
|
except dns.exception.DNSException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
else:
|
||||||
|
values.append(hostname)
|
||||||
|
|
||||||
return values
|
return values
|
||||||
|
|
||||||
@@ -1021,15 +1040,17 @@ def set_secondary_dns(hostnames, env):
|
|||||||
# Validate that all hostnames are valid and that all zone-xfer IP addresses are valid.
|
# Validate that all hostnames are valid and that all zone-xfer IP addresses are valid.
|
||||||
resolver = dns.resolver.get_default_resolver()
|
resolver = dns.resolver.get_default_resolver()
|
||||||
resolver.timeout = 5
|
resolver.timeout = 5
|
||||||
|
resolver.lifetime = 5
|
||||||
|
|
||||||
for item in hostnames:
|
for item in hostnames:
|
||||||
if not item.startswith("xfr:"):
|
if not item.startswith("xfr:"):
|
||||||
# Resolve hostname.
|
# Resolve hostname.
|
||||||
try:
|
try:
|
||||||
response = resolver.resolve(item, "A")
|
response = resolver.resolve(item, "A")
|
||||||
except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
|
except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.Timeout):
|
||||||
try:
|
try:
|
||||||
response = resolver.resolve(item, "AAAA")
|
response = resolver.resolve(item, "AAAA")
|
||||||
except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
|
except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.Timeout):
|
||||||
raise ValueError("Could not resolve the IP address of %s." % item)
|
raise ValueError("Could not resolve the IP address of %s." % item)
|
||||||
else:
|
else:
|
||||||
# Validate IP address.
|
# Validate IP address.
|
||||||
@@ -1062,7 +1083,7 @@ def get_custom_dns_records(custom_dns, qname, rtype):
|
|||||||
def build_recommended_dns(env):
|
def build_recommended_dns(env):
|
||||||
ret = []
|
ret = []
|
||||||
for (domain, zonefile, records) in build_zones(env):
|
for (domain, zonefile, records) in build_zones(env):
|
||||||
# remove records that we don't dislay
|
# remove records that we don't display
|
||||||
records = [r for r in records if r[3] is not False]
|
records = [r for r in records if r[3] is not False]
|
||||||
|
|
||||||
# put Required at the top, then Recommended, then everythiing else
|
# put Required at the top, then Recommended, then everythiing else
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ content = sys.stdin.read().strip()
|
|||||||
|
|
||||||
# If there's nothing coming in, just exit.
|
# If there's nothing coming in, just exit.
|
||||||
if content == "":
|
if content == "":
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# create MIME message
|
# create MIME message
|
||||||
msg = MIMEMultipart('alternative')
|
msg = MIMEMultipart('alternative')
|
||||||
@@ -41,7 +41,7 @@ msg['From'] = "\"%s\" <%s>" % (env['PRIMARY_HOSTNAME'], admin_addr)
|
|||||||
msg['To'] = admin_addr
|
msg['To'] = admin_addr
|
||||||
msg['Subject'] = "[%s] %s" % (env['PRIMARY_HOSTNAME'], subject)
|
msg['Subject'] = "[%s] %s" % (env['PRIMARY_HOSTNAME'], subject)
|
||||||
|
|
||||||
content_html = "<html><body><pre>{}</pre></body></html>".format(html.escape(content))
|
content_html = '<html><body><pre style="overflow-x: scroll; white-space: pre;">{}</pre></body></html>'.format(html.escape(content))
|
||||||
|
|
||||||
msg.attach(MIMEText(content, 'plain'))
|
msg.attach(MIMEText(content, 'plain'))
|
||||||
msg.attach(MIMEText(content_html, 'html'))
|
msg.attach(MIMEText(content_html, 'html'))
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ def scan_files(collector):
|
|||||||
continue
|
continue
|
||||||
elif fn[-3:] == '.gz':
|
elif fn[-3:] == '.gz':
|
||||||
tmp_file = tempfile.NamedTemporaryFile()
|
tmp_file = tempfile.NamedTemporaryFile()
|
||||||
shutil.copyfileobj(gzip.open(fn), tmp_file)
|
with gzip.open(fn, 'rb') as f:
|
||||||
|
shutil.copyfileobj(f, tmp_file)
|
||||||
|
|
||||||
if VERBOSE:
|
if VERBOSE:
|
||||||
print("Processing file", fn, "...")
|
print("Processing file", fn, "...")
|
||||||
|
|||||||
@@ -535,7 +535,8 @@ def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring
|
|||||||
# Second, check that the certificate matches the private key.
|
# Second, check that the certificate matches the private key.
|
||||||
if ssl_private_key is not None:
|
if ssl_private_key is not None:
|
||||||
try:
|
try:
|
||||||
priv_key = load_pem(open(ssl_private_key, 'rb').read())
|
with open(ssl_private_key, 'rb') as f:
|
||||||
|
priv_key = load_pem(f.read())
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return ("The private key file %s is not a private key file: %s" % (ssl_private_key, str(e)), None)
|
return ("The private key file %s is not a private key file: %s" % (ssl_private_key, str(e)), None)
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,12 @@ def run_services_checks(env, output, pool):
|
|||||||
fatal = fatal or fatal2
|
fatal = fatal or fatal2
|
||||||
output2.playback(output)
|
output2.playback(output)
|
||||||
|
|
||||||
|
# Check fail2ban.
|
||||||
|
code, ret = shell('check_output', ["fail2ban-client", "status"], capture_stderr=True, trap=True)
|
||||||
|
if code != 0:
|
||||||
|
output.print_error("fail2ban is not running.")
|
||||||
|
all_running = False
|
||||||
|
|
||||||
if all_running:
|
if all_running:
|
||||||
output.print_ok("All system services are running.")
|
output.print_ok("All system services are running.")
|
||||||
|
|
||||||
@@ -207,7 +213,8 @@ def check_ssh_password(env, output):
|
|||||||
# the configuration file.
|
# the configuration file.
|
||||||
if not os.path.exists("/etc/ssh/sshd_config"):
|
if not os.path.exists("/etc/ssh/sshd_config"):
|
||||||
return
|
return
|
||||||
sshd = open("/etc/ssh/sshd_config").read()
|
with open("/etc/ssh/sshd_config", "r") as f:
|
||||||
|
sshd = f.read()
|
||||||
if re.search("\nPasswordAuthentication\s+yes", sshd) \
|
if re.search("\nPasswordAuthentication\s+yes", sshd) \
|
||||||
or not re.search("\nPasswordAuthentication\s+no", sshd):
|
or not re.search("\nPasswordAuthentication\s+no", sshd):
|
||||||
output.print_error("""The SSH server on this machine permits password-based login. A more secure
|
output.print_error("""The SSH server on this machine permits password-based login. A more secure
|
||||||
@@ -308,6 +315,8 @@ def run_network_checks(env, output):
|
|||||||
output.print_ok("IP address is not blacklisted by zen.spamhaus.org.")
|
output.print_ok("IP address is not blacklisted by zen.spamhaus.org.")
|
||||||
elif zen == "[timeout]":
|
elif zen == "[timeout]":
|
||||||
output.print_warning("Connection to zen.spamhaus.org timed out. We could not determine whether your server's IP address is blacklisted. Please try again later.")
|
output.print_warning("Connection to zen.spamhaus.org timed out. We could not determine whether your server's IP address is blacklisted. Please try again later.")
|
||||||
|
elif zen == "[Not Set]":
|
||||||
|
output.print_warning("Could not connect to zen.spamhaus.org. We could not determine whether your server's IP address is blacklisted. Please try again later.")
|
||||||
else:
|
else:
|
||||||
output.print_error("""The IP address of this machine %s is listed in the Spamhaus Block List (code %s),
|
output.print_error("""The IP address of this machine %s is listed in the Spamhaus Block List (code %s),
|
||||||
which may prevent recipients from receiving your email. See http://www.spamhaus.org/query/ip/%s."""
|
which may prevent recipients from receiving your email. See http://www.spamhaus.org/query/ip/%s."""
|
||||||
@@ -541,7 +550,7 @@ def check_dns_zone(domain, env, output, dns_zonefiles):
|
|||||||
for ns in custom_secondary_ns:
|
for ns in custom_secondary_ns:
|
||||||
# We must first resolve the nameserver to an IP address so we can query it.
|
# We must first resolve the nameserver to an IP address so we can query it.
|
||||||
ns_ips = query_dns(ns, "A")
|
ns_ips = query_dns(ns, "A")
|
||||||
if not ns_ips:
|
if not ns_ips or ns_ips in {'[Not Set]', '[timeout]'}:
|
||||||
output.print_error("Secondary nameserver %s is not valid (it doesn't resolve to an IP address)." % ns)
|
output.print_error("Secondary nameserver %s is not valid (it doesn't resolve to an IP address)." % ns)
|
||||||
continue
|
continue
|
||||||
# Choose the first IP if nameserver returns multiple
|
# Choose the first IP if nameserver returns multiple
|
||||||
@@ -592,7 +601,8 @@ def check_dnssec(domain, env, output, dns_zonefiles, is_checking_primary=False):
|
|||||||
# record that we suggest using is for the KSK (and that's how the DS records were generated).
|
# record that we suggest using is for the KSK (and that's how the DS records were generated).
|
||||||
# We'll also give the nice name for the key algorithm.
|
# We'll also give the nice name for the key algorithm.
|
||||||
dnssec_keys = load_env_vars_from_file(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/%s.conf' % alg_name_map[ds_alg]))
|
dnssec_keys = load_env_vars_from_file(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/%s.conf' % alg_name_map[ds_alg]))
|
||||||
dnsssec_pubkey = open(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/' + dnssec_keys['KSK'] + '.key')).read().split("\t")[3].split(" ")[3]
|
with open(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/' + dnssec_keys['KSK'] + '.key'), 'r') as f:
|
||||||
|
dnsssec_pubkey = f.read().split("\t")[3].split(" ")[3]
|
||||||
|
|
||||||
expected_ds_records[ (ds_keytag, ds_alg, ds_digalg, ds_digest) ] = {
|
expected_ds_records[ (ds_keytag, ds_alg, ds_digalg, ds_digest) ] = {
|
||||||
"record": rr_ds,
|
"record": rr_ds,
|
||||||
@@ -744,6 +754,8 @@ def check_mail_domain(domain, env, output):
|
|||||||
output.print_ok("Domain is not blacklisted by dbl.spamhaus.org.")
|
output.print_ok("Domain is not blacklisted by dbl.spamhaus.org.")
|
||||||
elif dbl == "[timeout]":
|
elif dbl == "[timeout]":
|
||||||
output.print_warning("Connection to dbl.spamhaus.org timed out. We could not determine whether the domain {} is blacklisted. Please try again later.".format(domain))
|
output.print_warning("Connection to dbl.spamhaus.org timed out. We could not determine whether the domain {} is blacklisted. Please try again later.".format(domain))
|
||||||
|
elif dbl == "[Not Set]":
|
||||||
|
output.print_warning("Could not connect to dbl.spamhaus.org. We could not determine whether the domain {} is blacklisted. Please try again later.".format(domain))
|
||||||
else:
|
else:
|
||||||
output.print_error("""This domain is listed in the Spamhaus Domain Block List (code %s),
|
output.print_error("""This domain is listed in the Spamhaus Domain Block List (code %s),
|
||||||
which may prevent recipients from receiving your mail.
|
which may prevent recipients from receiving your mail.
|
||||||
@@ -788,12 +800,17 @@ def query_dns(qname, rtype, nxdomain='[Not Set]', at=None, as_list=False):
|
|||||||
# running bind server), or if the 'at' argument is specified, use that host
|
# running bind server), or if the 'at' argument is specified, use that host
|
||||||
# as the nameserver.
|
# as the nameserver.
|
||||||
resolver = dns.resolver.get_default_resolver()
|
resolver = dns.resolver.get_default_resolver()
|
||||||
if at:
|
|
||||||
|
# Make sure at is not a string that cannot be used as a nameserver
|
||||||
|
if at and at not in {'[Not set]', '[timeout]'}:
|
||||||
resolver = dns.resolver.Resolver()
|
resolver = dns.resolver.Resolver()
|
||||||
resolver.nameservers = [at]
|
resolver.nameservers = [at]
|
||||||
|
|
||||||
# Set a timeout so that a non-responsive server doesn't hold us back.
|
# Set a timeout so that a non-responsive server doesn't hold us back.
|
||||||
resolver.timeout = 5
|
resolver.timeout = 5
|
||||||
|
# The number of seconds to spend trying to get an answer to the question. If the
|
||||||
|
# lifetime expires a dns.exception.Timeout exception will be raised.
|
||||||
|
resolver.lifetime = 5
|
||||||
|
|
||||||
# Do the query.
|
# Do the query.
|
||||||
try:
|
try:
|
||||||
@@ -895,11 +912,11 @@ def list_apt_updates(apt_update=True):
|
|||||||
return pkgs
|
return pkgs
|
||||||
|
|
||||||
def what_version_is_this(env):
|
def what_version_is_this(env):
|
||||||
# This function runs `git describe --abbrev=0` on the Mail-in-a-Box installation directory.
|
# This function runs `git describe --always --abbrev=0` on the Mail-in-a-Box installation directory.
|
||||||
# Git may not be installed and Mail-in-a-Box may not have been cloned from github,
|
# Git may not be installed and Mail-in-a-Box may not have been cloned from github,
|
||||||
# so this function may raise all sorts of exceptions.
|
# so this function may raise all sorts of exceptions.
|
||||||
miab_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
miab_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
tag = shell("check_output", ["/usr/bin/git", "describe", "--abbrev=0"], env={"GIT_DIR": os.path.join(miab_dir, '.git')}).strip()
|
tag = shell("check_output", ["/usr/bin/git", "describe", "--always", "--abbrev=0"], env={"GIT_DIR": os.path.join(miab_dir, '.git')}).strip()
|
||||||
return tag
|
return tag
|
||||||
|
|
||||||
def get_latest_miab_version():
|
def get_latest_miab_version():
|
||||||
@@ -947,7 +964,8 @@ def run_and_output_changes(env, pool):
|
|||||||
# Load previously saved status checks.
|
# Load previously saved status checks.
|
||||||
cache_fn = "/var/cache/mailinabox/status_checks.json"
|
cache_fn = "/var/cache/mailinabox/status_checks.json"
|
||||||
if os.path.exists(cache_fn):
|
if os.path.exists(cache_fn):
|
||||||
prev = json.load(open(cache_fn))
|
with open(cache_fn, 'r') as f:
|
||||||
|
prev = json.load(f)
|
||||||
|
|
||||||
# Group the serial output into categories by the headings.
|
# Group the serial output into categories by the headings.
|
||||||
def group_by_heading(lines):
|
def group_by_heading(lines):
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<h3>Add a mail alias</h3>
|
<h3>Add a mail alias</h3>
|
||||||
|
|
||||||
<p>Aliases are email forwarders. An alias can forward email to a <a href="#" onclick="return show_panel('users')">mail user</a> or to any email address.</p>
|
<p>Aliases are email forwarders. An alias can forward email to a <a href="#users">mail user</a> or to any email address.</p>
|
||||||
|
|
||||||
<p>To use an alias or any address besides your own login username in outbound mail, the sending user must be included as a permitted sender for the alias.</p>
|
<p>To use an alias or any address besides your own login username in outbound mail, the sending user must be included as a permitted sender for the alias.</p>
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
|
|
||||||
<h3>Using a secondary nameserver</h3>
|
<h3>Using a secondary nameserver</h3>
|
||||||
|
|
||||||
<p>If your TLD requires you to have two separate nameservers, you can either set up <a href="#" onclick="return show_panel('external_dns')">external DNS</a> and ignore the DNS server on this box entirely, or use the DNS server on this box but add a secondary (aka “slave”) nameserver.</p>
|
<p>If your TLD requires you to have two separate nameservers, you can either set up <a href="#external_dns">external DNS</a> and ignore the DNS server on this box entirely, or use the DNS server on this box but add a secondary (aka “slave”) nameserver.</p>
|
||||||
<p>If you choose to use a secondary nameserver, you must find a secondary nameserver service provider. Your domain name registrar or virtual cloud provider may provide this service for you. Once you set up the secondary nameserver service, enter the hostname (not the IP address) of <em>their</em> secondary nameserver in the box below.</p>
|
<p>If you choose to use a secondary nameserver, you must find a secondary nameserver service provider. Your domain name registrar or virtual cloud provider may provide this service for you. Once you set up the secondary nameserver service, enter the hostname (not the IP address) of <em>their</em> secondary nameserver in the box below.</p>
|
||||||
|
|
||||||
<form class="form-horizontal" role="form" onsubmit="do_set_secondary_dns(); return false;">
|
<form class="form-horizontal" role="form" onsubmit="do_set_secondary_dns(); return false;">
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
<div class="col-sm-offset-1 col-sm-11">
|
<div class="col-sm-offset-1 col-sm-11">
|
||||||
<p class="small">
|
<p class="small">
|
||||||
Multiple secondary servers can be separated with commas or spaces (i.e., <code>ns2.hostingcompany.com ns3.hostingcompany.com</code>).
|
Multiple secondary servers can be separated with commas or spaces (i.e., <code>ns2.hostingcompany.com ns3.hostingcompany.com</code>).
|
||||||
To enable zone transfers to additional servers without listing them as secondary nameservers, add an IP address or subnet using <code>xfr:10.20.30.40</code> or <code>xfr:10.0.0.0/8</code>.
|
To enable zone transfers to additional servers without listing them as secondary nameservers, prefix a hostname, IP address, or subnet with <code>xfr:</code>, e.g. <code>xfr:10.20.30.40</code> or <code>xfr:10.0.0.0/8</code>.
|
||||||
</p>
|
</p>
|
||||||
<p id="secondarydns-clear-instructions" style="display: none" class="small">
|
<p id="secondarydns-clear-instructions" style="display: none" class="small">
|
||||||
Clear the input field above and click Update to use this machine itself as secondary DNS, which is the default/normal setup.
|
Clear the input field above and click Update to use this machine itself as secondary DNS, which is the default/normal setup.
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
|
|
||||||
<link rel="stylesheet" href="/admin/assets/bootstrap/css/bootstrap.min.css">
|
<link rel="stylesheet" href="/admin/assets/bootstrap/css/bootstrap.min.css">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@@ -36,20 +36,20 @@
|
|||||||
margin-bottom: 13px;
|
margin-bottom: 13px;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
.panel-heading h3 {
|
.panel-heading h3 {
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
font-size: 110%;
|
font-size: 110%;
|
||||||
margin-bottom: 13px;
|
margin-bottom: 13px;
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
}
|
}
|
||||||
h4:first-child {
|
h4:first-child {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin_panel {
|
.admin_panel {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -59,10 +59,10 @@
|
|||||||
margin: 1.5em 0;
|
margin: 1.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ol li {
|
ol li {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.if-logged-in { display: none; }
|
.if-logged-in { display: none; }
|
||||||
.if-logged-in-admin { display: none; }
|
.if-logged-in-admin { display: none; }
|
||||||
|
|
||||||
@@ -73,21 +73,16 @@
|
|||||||
filter: invert(100%) hue-rotate(180deg);
|
filter: invert(100%) hue-rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set explicit background color (necessary for Firefox) */
|
|
||||||
html {
|
|
||||||
background-color: #111;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Override Boostrap theme here to give more contrast. The black turns to white by the filter. */
|
/* Override Boostrap theme here to give more contrast. The black turns to white by the filter. */
|
||||||
.form-control {
|
.form-control {
|
||||||
color: black !important;
|
color: black !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Revert the invert for the navbar */
|
/* Revert the invert for the navbar */
|
||||||
button, div.navbar {
|
button, div.navbar {
|
||||||
filter: invert(100%) hue-rotate(180deg);
|
filter: invert(100%) hue-rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Revert the revert for the dropdowns */
|
/* Revert the revert for the dropdowns */
|
||||||
ul.dropdown-menu {
|
ul.dropdown-menu {
|
||||||
filter: invert(100%) hue-rotate(180deg);
|
filter: invert(100%) hue-rotate(180deg);
|
||||||
@@ -117,30 +112,30 @@
|
|||||||
<li class="dropdown if-logged-in-admin">
|
<li class="dropdown if-logged-in-admin">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">System <b class="caret"></b></a>
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">System <b class="caret"></b></a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a href="#system_status" onclick="return show_panel(this);">Status Checks</a></li>
|
<li><a href="#system_status">Status Checks</a></li>
|
||||||
<li><a href="#tls" onclick="return show_panel(this);">TLS (SSL) Certificates</a></li>
|
<li><a href="#tls">TLS (SSL) Certificates</a></li>
|
||||||
<li><a href="#system_backup" onclick="return show_panel(this);">Backup Status</a></li>
|
<li><a href="#system_backup">Backup Status</a></li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li class="dropdown-header">Advanced Pages</li>
|
<li class="dropdown-header">Advanced Pages</li>
|
||||||
<li><a href="#custom_dns" onclick="return show_panel(this);">Custom DNS</a></li>
|
<li><a href="#custom_dns">Custom DNS</a></li>
|
||||||
<li><a href="#external_dns" onclick="return show_panel(this);">External DNS</a></li>
|
<li><a href="#external_dns">External DNS</a></li>
|
||||||
<li><a href="#munin" onclick="return show_panel(this);">Munin Monitoring</a></li>
|
<li><a href="#munin">Munin Monitoring</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#mail-guide" onclick="return show_panel(this);" class="if-logged-in-not-admin">Mail</a></li>
|
<li><a href="#mail-guide" class="if-logged-in-not-admin">Mail</a></li>
|
||||||
<li class="dropdown if-logged-in-admin">
|
<li class="dropdown if-logged-in-admin">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Mail & Users <b class="caret"></b></a>
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Mail & Users <b class="caret"></b></a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a href="#mail-guide" onclick="return show_panel(this);">Instructions</a></li>
|
<li><a href="#mail-guide">Instructions</a></li>
|
||||||
<li><a href="#users" onclick="return show_panel(this);">Users</a></li>
|
<li><a href="#users">Users</a></li>
|
||||||
<li><a href="#aliases" onclick="return show_panel(this);">Aliases</a></li>
|
<li><a href="#aliases">Aliases</a></li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li class="dropdown-header">Your Account</li>
|
<li class="dropdown-header">Your Account</li>
|
||||||
<li><a href="#mfa" onclick="return show_panel(this);">Two-Factor Authentication</a></li>
|
<li><a href="#mfa">Two-Factor Authentication</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#sync_guide" onclick="return show_panel(this);" class="if-logged-in">Contacts/Calendar</a></li>
|
<li><a href="#sync_guide" class="if-logged-in">Contacts/Calendar</a></li>
|
||||||
<li><a href="#web" onclick="return show_panel(this);" class="if-logged-in-admin">Web</a></li>
|
<li><a href="#web" class="if-logged-in-admin">Web</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
<li class="if-logged-in"><a href="#" onclick="do_logout(); return false;" style="color: white">Log out</a></li>
|
<li class="if-logged-in"><a href="#" onclick="do_logout(); return false;" style="color: white">Log out</a></li>
|
||||||
@@ -426,23 +421,25 @@ function do_logout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function show_panel(panelid) {
|
function show_panel(panelid) {
|
||||||
if (panelid.getAttribute)
|
if (panelid.getAttribute) {
|
||||||
// we might be passed an HTMLElement <a>.
|
// we might be passed an HTMLElement <a>.
|
||||||
panelid = panelid.getAttribute('href').substring(1);
|
panelid = panelid.getAttribute('href').substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
$('.admin_panel').hide();
|
$('.admin_panel').hide();
|
||||||
$('#panel_' + panelid).show();
|
$('#panel_' + panelid).show();
|
||||||
if (typeof localStorage != 'undefined')
|
|
||||||
localStorage.setItem("miab-cp-lastpanel", panelid);
|
|
||||||
if (window["show_" + panelid])
|
if (window["show_" + panelid])
|
||||||
window["show_" + panelid]();
|
window["show_" + panelid]();
|
||||||
|
|
||||||
current_panel = panelid;
|
current_panel = panelid;
|
||||||
switch_back_to_panel = null;
|
switch_back_to_panel = null;
|
||||||
|
|
||||||
return false; // when called from onclick, cancel navigation
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.onhashchange = function() {
|
||||||
|
var panelid = window.location.hash.substring(1);
|
||||||
|
show_panel(panelid);
|
||||||
|
};
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
// Recall saved user credentials.
|
// Recall saved user credentials.
|
||||||
try {
|
try {
|
||||||
@@ -457,8 +454,9 @@ $(function() {
|
|||||||
show_hide_menus();
|
show_hide_menus();
|
||||||
|
|
||||||
// Recall what the user was last looking at.
|
// Recall what the user was last looking at.
|
||||||
if (api_credentials != null && typeof localStorage != 'undefined' && localStorage.getItem("miab-cp-lastpanel")) {
|
if (api_credentials != null && window.location.hash) {
|
||||||
show_panel(localStorage.getItem("miab-cp-lastpanel"));
|
var panelid = window.location.hash.substring(1);
|
||||||
|
show_panel(panelid);
|
||||||
} else if (api_credentials != null) {
|
} else if (api_credentials != null) {
|
||||||
show_panel('welcome');
|
show_panel('welcome');
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -168,7 +168,18 @@ function do_login() {
|
|||||||
// Open the next panel the user wants to go to. Do this after the XHR response
|
// Open the next panel the user wants to go to. Do this after the XHR response
|
||||||
// is over so that we don't start a new XHR request while this one is finishing,
|
// is over so that we don't start a new XHR request while this one is finishing,
|
||||||
// which confuses the loading indicator.
|
// which confuses the loading indicator.
|
||||||
setTimeout(function() { show_panel(!switch_back_to_panel || switch_back_to_panel == "login" ? 'welcome' : switch_back_to_panel) }, 300);
|
setTimeout(function() {
|
||||||
|
if (window.location.hash) {
|
||||||
|
var panelid = window.location.hash.substring(1);
|
||||||
|
show_panel(panelid);
|
||||||
|
} else {
|
||||||
|
show_panel(
|
||||||
|
!switch_back_to_panel || switch_back_to_panel == "login"
|
||||||
|
? 'welcome'
|
||||||
|
: switch_back_to_panel)
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<tr><th>Password:</th> <td>Your mail password.</td></tr>
|
<tr><th>Password:</th> <td>Your mail password.</td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p>In addition to setting up your email, you’ll also need to set up <a href="#sync_guide" onclick="return show_panel(this);">contacts and calendar synchronization</a> separately.</p>
|
<p>In addition to setting up your email, you’ll also need to set up <a href="#sync_guide">contacts and calendar synchronization</a> separately.</p>
|
||||||
|
|
||||||
<p>As an alternative to IMAP you can also use the POP protocol: choose POP as the protocol, port 995, and SSL or TLS security in your mail client. The SMTP settings and usernames and passwords remain the same. However, we recommend you use IMAP instead.</p>
|
<p>As an alternative to IMAP you can also use the POP protocol: choose POP as the protocol, port 995, and SSL or TLS security in your mail client. The SMTP settings and usernames and passwords remain the same. However, we recommend you use IMAP instead.</p>
|
||||||
|
|
||||||
|
|||||||
@@ -17,14 +17,14 @@
|
|||||||
<tr><th>Calendar</td> <td><a href="https://{{hostname}}/cloud/calendar">https://{{hostname}}/cloud/calendar</a></td></tr>
|
<tr><th>Calendar</td> <td><a href="https://{{hostname}}/cloud/calendar">https://{{hostname}}/cloud/calendar</a></td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p>Log in settings are the same as with <a href="#mail-guide" onclick="return show_panel(this);">mail</a>: your
|
<p>Log in settings are the same as with <a href="#mail-guide">mail</a>: your
|
||||||
complete email address and your mail password.</p>
|
complete email address and your mail password.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<h4>On your mobile device</h4>
|
<h4>On your mobile device</h4>
|
||||||
|
|
||||||
<p>If you set up your <a href="#mail-guide" onclick="return show_panel(this);">mail</a> using Exchange/ActiveSync,
|
<p>If you set up your <a href="#mail-guide">mail</a> using Exchange/ActiveSync,
|
||||||
your contacts and calendar may already appear on your device.</p>
|
your contacts and calendar may already appear on your device.</p>
|
||||||
<p>Otherwise, here are some apps that can synchronize your contacts and calendar to your Android phone.</p>
|
<p>Otherwise, here are some apps that can synchronize your contacts and calendar to your Android phone.</p>
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<h2>Backup Status</h2>
|
<h2>Backup Status</h2>
|
||||||
|
|
||||||
<p>The box makes an incremental backup each night. By default the backup is stored on the machine itself, but you can also store it on S3-compatible services like Amazon Web Services (AWS).</p>
|
<p>The box makes an incremental backup each night. You can store the backup on any Amazon Web Services S3-compatible service, or other options.</p>
|
||||||
|
|
||||||
<h3>Configuration</h3>
|
<h3>Configuration</h3>
|
||||||
|
|
||||||
@@ -45,6 +45,10 @@
|
|||||||
<label for="backup-target-rsync-host" class="col-sm-2 control-label">Hostname</label>
|
<label for="backup-target-rsync-host" class="col-sm-2 control-label">Hostname</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<input type="text" placeholder="hostname.local" class="form-control" rows="1" id="backup-target-rsync-host">
|
<input type="text" placeholder="hostname.local" class="form-control" rows="1" id="backup-target-rsync-host">
|
||||||
|
<div class="small" style="margin-top: 2px">
|
||||||
|
The hostname at your rsync provider, e.g. <tt>da2327.rsync.net</tt>. Optionally includes a colon
|
||||||
|
and the provider's non-standard ssh port number, e.g. <tt>u215843.your-storagebox.de:23</tt>.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group backup-target-rsync">
|
<div class="form-group backup-target-rsync">
|
||||||
@@ -66,9 +70,12 @@
|
|||||||
<div class="small" style="margin-top: 2px">
|
<div class="small" style="margin-top: 2px">
|
||||||
Copy the Public SSH Key above, and paste it within the <tt>~/.ssh/authorized_keys</tt>
|
Copy the Public SSH Key above, and paste it within the <tt>~/.ssh/authorized_keys</tt>
|
||||||
of target user on the backup server specified above. That way you'll enable secure and
|
of target user on the backup server specified above. That way you'll enable secure and
|
||||||
passwordless authentication from your mail-in-a-box server and your backup server.
|
passwordless authentication from your Mail-in-a-Box server and your backup server.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="copy_pub_key_div" class="col-sm">
|
||||||
|
<button type="button" class="btn btn-small" onclick="copy_pub_key_to_clipboard()">Copy</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- S3 BACKUP -->
|
<!-- S3 BACKUP -->
|
||||||
<div class="form-group backup-target-s3">
|
<div class="form-group backup-target-s3">
|
||||||
@@ -91,13 +98,19 @@
|
|||||||
<div class="form-group backup-target-s3">
|
<div class="form-group backup-target-s3">
|
||||||
<label for="backup-target-s3-host" class="col-sm-2 control-label">S3 Host / Endpoint</label>
|
<label for="backup-target-s3-host" class="col-sm-2 control-label">S3 Host / Endpoint</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<input type="text" placeholder="Endpoint" class="form-control" rows="1" id="backup-target-s3-host">
|
<input type="text" placeholder="https://s3.backuphost.com" class="form-control" rows="1" id="backup-target-s3-host">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group backup-target-s3">
|
<div class="form-group backup-target-s3">
|
||||||
<label for="backup-target-s3-path" class="col-sm-2 control-label">S3 Path</label>
|
<label for="backup-target-s3-region-name" class="col-sm-2 control-label">S3 Region Name <span style="font-weight: normal">(if required)</span></label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<input type="text" placeholder="your-bucket-name/backup-directory" class="form-control" rows="1" id="backup-target-s3-path">
|
<input type="text" placeholder="region.name" class="form-control" rows="1" id="backup-target-s3-region-name">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group backup-target-s3">
|
||||||
|
<label for="backup-target-s3-path" class="col-sm-2 control-label">S3 Bucket & Path</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input type="text" placeholder="bucket-name/backup-directory" class="form-control" rows="1" id="backup-target-s3-path">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group backup-target-s3">
|
<div class="form-group backup-target-s3">
|
||||||
@@ -259,19 +272,18 @@ function show_custom_backup() {
|
|||||||
} else if (r.target == "off") {
|
} else if (r.target == "off") {
|
||||||
$("#backup-target-type").val("off");
|
$("#backup-target-type").val("off");
|
||||||
} else if (r.target.substring(0, 8) == "rsync://") {
|
} else if (r.target.substring(0, 8) == "rsync://") {
|
||||||
$("#backup-target-type").val("rsync");
|
const spec = url_split(r.target);
|
||||||
var path = r.target.substring(8).split('//');
|
$("#backup-target-type").val(spec.scheme);
|
||||||
var host_parts = path.shift().split('@');
|
$("#backup-target-rsync-user").val(spec.user);
|
||||||
$("#backup-target-rsync-user").val(host_parts[0]);
|
$("#backup-target-rsync-host").val(spec.host);
|
||||||
$("#backup-target-rsync-host").val(host_parts[1]);
|
$("#backup-target-rsync-path").val(spec.path);
|
||||||
$("#backup-target-rsync-path").val('/'+path[0]);
|
|
||||||
} else if (r.target.substring(0, 5) == "s3://") {
|
} else if (r.target.substring(0, 5) == "s3://") {
|
||||||
|
const spec = url_split(r.target);
|
||||||
$("#backup-target-type").val("s3");
|
$("#backup-target-type").val("s3");
|
||||||
var hostpath = r.target.substring(5).split('/');
|
$("#backup-target-s3-host-select").val(spec.host);
|
||||||
var host = hostpath.shift();
|
$("#backup-target-s3-host").val(spec.host);
|
||||||
$("#backup-target-s3-host-select").val(host);
|
$("#backup-target-s3-region-name").val(spec.user); // stuffing the region name in the username
|
||||||
$("#backup-target-s3-host").val(host);
|
$("#backup-target-s3-path").val(spec.path);
|
||||||
$("#backup-target-s3-path").val(hostpath.join('/'));
|
|
||||||
} else if (r.target.substring(0, 5) == "b2://") {
|
} else if (r.target.substring(0, 5) == "b2://") {
|
||||||
$("#backup-target-type").val("b2");
|
$("#backup-target-type").val("b2");
|
||||||
var targetPath = r.target.substring(5);
|
var targetPath = r.target.substring(5);
|
||||||
@@ -279,7 +291,7 @@ function show_custom_backup() {
|
|||||||
var b2_applicationkey = targetPath.split(':')[1].split('@')[0];
|
var b2_applicationkey = targetPath.split(':')[1].split('@')[0];
|
||||||
var b2_bucket = targetPath.split('@')[1];
|
var b2_bucket = targetPath.split('@')[1];
|
||||||
$("#backup-target-b2-user").val(b2_application_keyid);
|
$("#backup-target-b2-user").val(b2_application_keyid);
|
||||||
$("#backup-target-b2-pass").val(b2_applicationkey);
|
$("#backup-target-b2-pass").val(decodeURIComponent(b2_applicationkey));
|
||||||
$("#backup-target-b2-bucket").val(b2_bucket);
|
$("#backup-target-b2-bucket").val(b2_bucket);
|
||||||
}
|
}
|
||||||
toggle_form()
|
toggle_form()
|
||||||
@@ -295,13 +307,16 @@ function set_custom_backup() {
|
|||||||
if (target_type == "local" || target_type == "off")
|
if (target_type == "local" || target_type == "off")
|
||||||
target = target_type;
|
target = target_type;
|
||||||
else if (target_type == "s3")
|
else if (target_type == "s3")
|
||||||
target = "s3://" + $("#backup-target-s3-host").val() + "/" + $("#backup-target-s3-path").val();
|
target = "s3://"
|
||||||
|
+ ($("#backup-target-s3-region-name").val() ? ($("#backup-target-s3-region-name").val() + "@") : "")
|
||||||
|
+ $("#backup-target-s3-host").val()
|
||||||
|
+ "/" + $("#backup-target-s3-path").val();
|
||||||
else if (target_type == "rsync") {
|
else if (target_type == "rsync") {
|
||||||
target = "rsync://" + $("#backup-target-rsync-user").val() + "@" + $("#backup-target-rsync-host").val()
|
target = "rsync://" + $("#backup-target-rsync-user").val() + "@" + $("#backup-target-rsync-host").val()
|
||||||
+ "/" + $("#backup-target-rsync-path").val();
|
+ "/" + $("#backup-target-rsync-path").val();
|
||||||
target_user = '';
|
target_user = '';
|
||||||
} else if (target_type == "b2") {
|
} else if (target_type == "b2") {
|
||||||
target = 'b2://' + $('#backup-target-b2-user').val() + ':' + $('#backup-target-b2-pass').val()
|
target = 'b2://' + $('#backup-target-b2-user').val() + ':' + encodeURIComponent($('#backup-target-b2-pass').val())
|
||||||
+ '@' + $('#backup-target-b2-bucket').val()
|
+ '@' + $('#backup-target-b2-bucket').val()
|
||||||
target_user = '';
|
target_user = '';
|
||||||
target_pass = '';
|
target_pass = '';
|
||||||
@@ -344,4 +359,42 @@ function init_inputs(target_type) {
|
|||||||
set_host($('#backup-target-s3-host-select').val());
|
set_host($('#backup-target-s3-host-select').val());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return a two-element array of the substring preceding and the substring following
|
||||||
|
// the first occurence of separator in string. Return [undefined, string] if the
|
||||||
|
// separator does not appear in string.
|
||||||
|
const split1_rest = (string, separator) => {
|
||||||
|
const index = string.indexOf(separator);
|
||||||
|
return (index >= 0) ? [string.substring(0, index), string.substring(index + separator.length)] : [undefined, string];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note: The manifest JS URL class does not work in some security-conscious
|
||||||
|
// settings, e.g. Brave browser, so we roll our own that handles only what we need.
|
||||||
|
//
|
||||||
|
// Use greedy separator parsing to get parts of a MIAB backup target url.
|
||||||
|
// Note: path will not include a leading forward slash '/'
|
||||||
|
const url_split = url => {
|
||||||
|
const [ scheme, scheme_rest ] = split1_rest(url, '://');
|
||||||
|
const [ user, user_rest ] = split1_rest(scheme_rest, '@');
|
||||||
|
const [ host, path ] = split1_rest(user_rest, '/');
|
||||||
|
|
||||||
|
return {
|
||||||
|
scheme,
|
||||||
|
user,
|
||||||
|
host,
|
||||||
|
path,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hide Copy button if not in a modern clipboard-supporting environment.
|
||||||
|
// Using document API because jQuery is not necessarily available in this script scope.
|
||||||
|
if (!(navigator && navigator.clipboard && navigator.clipboard.writeText)) {
|
||||||
|
document.getElementById('copy_pub_key_div').hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copy_pub_key_to_clipboard() {
|
||||||
|
const ssh_pub_key = $("#ssh-pub-key").val();
|
||||||
|
navigator.clipboard.writeText(ssh_pub_key);
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,13 +10,13 @@
|
|||||||
border-top: none;
|
border-top: none;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
#system-checks .status-error td {
|
#system-checks .status-error td, .summary-error {
|
||||||
color: #733;
|
color: #733;
|
||||||
}
|
}
|
||||||
#system-checks .status-warning td {
|
#system-checks .status-warning td, .summary-warning {
|
||||||
color: #770;
|
color: #770;
|
||||||
}
|
}
|
||||||
#system-checks .status-ok td {
|
#system-checks .status-ok td, .summary-ok {
|
||||||
color: #040;
|
color: #040;
|
||||||
}
|
}
|
||||||
#system-checks div.extra {
|
#system-checks div.extra {
|
||||||
@@ -52,6 +52,9 @@
|
|||||||
</div> <!-- /col -->
|
</div> <!-- /col -->
|
||||||
<div class="col-md-pull-3 col-md-8">
|
<div class="col-md-pull-3 col-md-8">
|
||||||
|
|
||||||
|
<div id="system-checks-summary">
|
||||||
|
</div>
|
||||||
|
|
||||||
<table id="system-checks" class="table" style="max-width: 60em">
|
<table id="system-checks" class="table" style="max-width: 60em">
|
||||||
<thead>
|
<thead>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -64,6 +67,9 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
function show_system_status() {
|
function show_system_status() {
|
||||||
|
const summary = $('#system-checks-summary');
|
||||||
|
summary.html("");
|
||||||
|
|
||||||
$('#system-checks tbody').html("<tr><td colspan='2' class='text-muted'>Loading...</td></tr>")
|
$('#system-checks tbody').html("<tr><td colspan='2' class='text-muted'>Loading...</td></tr>")
|
||||||
|
|
||||||
api(
|
api(
|
||||||
@@ -93,6 +99,12 @@ function show_system_status() {
|
|||||||
{ },
|
{ },
|
||||||
function(r) {
|
function(r) {
|
||||||
$('#system-checks tbody').html("");
|
$('#system-checks tbody').html("");
|
||||||
|
const ok_symbol = "✓";
|
||||||
|
const error_symbol = "✖";
|
||||||
|
const warning_symbol = "?";
|
||||||
|
|
||||||
|
let count_by_status = { ok: 0, error: 0, warning: 0 };
|
||||||
|
|
||||||
for (var i = 0; i < r.length; i++) {
|
for (var i = 0; i < r.length; i++) {
|
||||||
var n = $("<tr><td class='status'/><td class='message'><p style='margin: 0'/><div class='extra'/><a class='showhide' href='#'/></tr>");
|
var n = $("<tr><td class='status'/><td class='message'><p style='margin: 0'/><div class='extra'/><a class='showhide' href='#'/></tr>");
|
||||||
if (i == 0) n.addClass('first')
|
if (i == 0) n.addClass('first')
|
||||||
@@ -100,9 +112,12 @@ function show_system_status() {
|
|||||||
n.addClass(r[i].type)
|
n.addClass(r[i].type)
|
||||||
else
|
else
|
||||||
n.addClass("status-" + r[i].type)
|
n.addClass("status-" + r[i].type)
|
||||||
if (r[i].type == "ok") n.find('td.status').text("✓")
|
|
||||||
if (r[i].type == "error") n.find('td.status').text("✖")
|
if (r[i].type == "ok") n.find('td.status').text(ok_symbol);
|
||||||
if (r[i].type == "warning") n.find('td.status').text("?")
|
if (r[i].type == "error") n.find('td.status').text(error_symbol);
|
||||||
|
if (r[i].type == "warning") n.find('td.status').text(warning_symbol);
|
||||||
|
count_by_status[r[i].type]++;
|
||||||
|
|
||||||
n.find('td.message p').text(r[i].text)
|
n.find('td.message p').text(r[i].text)
|
||||||
$('#system-checks tbody').append(n);
|
$('#system-checks tbody').append(n);
|
||||||
|
|
||||||
@@ -122,8 +137,17 @@ function show_system_status() {
|
|||||||
n.find('> td.message > div').append(m);
|
n.find('> td.message > div').append(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
|
// Summary counts
|
||||||
|
summary.html("Summary: ");
|
||||||
|
if (count_by_status['error'] + count_by_status['warning'] == 0) {
|
||||||
|
summary.append($('<span class="summary-ok"/>').text(`All ${count_by_status['ok']} ${ok_symbol} OK`));
|
||||||
|
} else {
|
||||||
|
summary.append($('<span class="summary-ok"/>').text(`${count_by_status['ok']} ${ok_symbol} OK, `));
|
||||||
|
summary.append($('<span class="summary-error"/>').text(`${count_by_status['error']} ${error_symbol} Error, `));
|
||||||
|
summary.append($('<span class="summary-warning"/>').text(`${count_by_status['warning']} ${warning_symbol} Warning`));
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var current_privacy_setting = null;
|
var current_privacy_setting = null;
|
||||||
|
|||||||
@@ -31,9 +31,9 @@
|
|||||||
</form>
|
</form>
|
||||||
<ul style="margin-top: 1em; padding-left: 1.5em; font-size: 90%;">
|
<ul style="margin-top: 1em; padding-left: 1.5em; font-size: 90%;">
|
||||||
<li>Passwords must be at least eight characters consisting of English letters and numbers only. For best results, <a href="#" onclick="return generate_random_password()">generate a random password</a>.</li>
|
<li>Passwords must be at least eight characters consisting of English letters and numbers only. For best results, <a href="#" onclick="return generate_random_password()">generate a random password</a>.</li>
|
||||||
<li>Use <a href="#" onclick="return show_panel('aliases')">aliases</a> to create email addresses that forward to existing accounts.</li>
|
<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>Administrators get access to this control panel.</li>
|
||||||
<li>User accounts cannot contain any international (non-ASCII) characters, but <a href="#" onclick="return show_panel('aliases');">aliases</a> can.</li>
|
<li>User accounts cannot contain any international (non-ASCII) characters, but <a href="#aliases">aliases</a> can.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>Existing mail users</h3>
|
<h3>Existing mail users</h3>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<p>You can replace the default website with your own HTML pages and other static files. This control panel won’t help you design a website, but once you have <tt>.html</tt> files you can upload them following these instructions:</p>
|
<p>You can replace the default website with your own HTML pages and other static files. This control panel won’t help you design a website, but once you have <tt>.html</tt> files you can upload them following these instructions:</p>
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>Ensure that any domains you are publishing a website for have no problems on the <a href="#system_status" onclick="return show_panel(this);">Status Checks</a> page.</li>
|
<li>Ensure that any domains you are publishing a website for have no problems on the <a href="#system_status">Status Checks</a> page.</li>
|
||||||
|
|
||||||
<li>On your personal computer, install an SSH file transfer program such as <a href="https://filezilla-project.org/">FileZilla</a> or <a href="http://linuxcommand.org/man_pages/scp1.html">scp</a>.</li>
|
<li>On your personal computer, install an SSH file transfer program such as <a href="https://filezilla-project.org/">FileZilla</a> or <a href="http://linuxcommand.org/man_pages/scp1.html">scp</a>.</li>
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p>To add a domain to this table, create a dummy <a href="#users" onclick="return show_panel(this);">mail user</a> or <a href="#aliases" onclick="return show_panel(this);">alias</a> on the domain first and see the <a href="https://mailinabox.email/guide.html#domain-name-configuration">setup guide</a> for adding nameserver records to the new domain at your registrar (but <i>not</i> glue records).</p>
|
<p>To add a domain to this table, create a dummy <a href="#users">mail user</a> or <a href="#aliases">alias</a> on the domain first and see the <a href="https://mailinabox.email/guide.html#domain-name-configuration">setup guide</a> for adding nameserver records to the new domain at your registrar (but <i>not</i> glue records).</p>
|
||||||
|
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ def load_env_vars_from_file(fn):
|
|||||||
# Load settings from a KEY=VALUE file.
|
# Load settings from a KEY=VALUE file.
|
||||||
import collections
|
import collections
|
||||||
env = collections.OrderedDict()
|
env = collections.OrderedDict()
|
||||||
for line in open(fn): env.setdefault(*line.strip().split("=", 1))
|
with open(fn, 'r') as f:
|
||||||
|
for line in f:
|
||||||
|
env.setdefault(*line.strip().split("=", 1))
|
||||||
return env
|
return env
|
||||||
|
|
||||||
def save_environment(env):
|
def save_environment(env):
|
||||||
@@ -34,7 +36,8 @@ def load_settings(env):
|
|||||||
import rtyaml
|
import rtyaml
|
||||||
fn = os.path.join(env['STORAGE_ROOT'], 'settings.yaml')
|
fn = os.path.join(env['STORAGE_ROOT'], 'settings.yaml')
|
||||||
try:
|
try:
|
||||||
config = rtyaml.load(open(fn, "r"))
|
with open(fn, "r") as f:
|
||||||
|
config = rtyaml.load(f)
|
||||||
if not isinstance(config, dict): raise ValueError() # caught below
|
if not isinstance(config, dict): raise ValueError() # caught below
|
||||||
return config
|
return config
|
||||||
except:
|
except:
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ def get_web_domains_with_root_overrides(env):
|
|||||||
root_overrides = { }
|
root_overrides = { }
|
||||||
nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
|
nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
|
||||||
if os.path.exists(nginx_conf_custom_fn):
|
if os.path.exists(nginx_conf_custom_fn):
|
||||||
custom_settings = rtyaml.load(open(nginx_conf_custom_fn))
|
with open(nginx_conf_custom_fn, 'r') as f:
|
||||||
|
custom_settings = rtyaml.load(f)
|
||||||
for domain, settings in custom_settings.items():
|
for domain, settings in custom_settings.items():
|
||||||
for type, value in [('redirect', settings.get('redirects', {}).get('/')),
|
for type, value in [('redirect', settings.get('redirects', {}).get('/')),
|
||||||
('proxy', settings.get('proxies', {}).get('/'))]:
|
('proxy', settings.get('proxies', {}).get('/'))]:
|
||||||
@@ -75,13 +76,18 @@ def do_web_update(env):
|
|||||||
# Pre-load what SSL certificates we will use for each domain.
|
# Pre-load what SSL certificates we will use for each domain.
|
||||||
ssl_certificates = get_ssl_certificates(env)
|
ssl_certificates = get_ssl_certificates(env)
|
||||||
|
|
||||||
|
# Helper for reading config files and templates
|
||||||
|
def read_conf(conf_fn):
|
||||||
|
with open(os.path.join(os.path.dirname(__file__), "../conf", conf_fn), "r") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
# Build an nginx configuration file.
|
# Build an nginx configuration file.
|
||||||
nginx_conf = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-top.conf")).read()
|
nginx_conf = read_conf("nginx-top.conf")
|
||||||
|
|
||||||
# Load the templates.
|
# Load the templates.
|
||||||
template0 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx.conf")).read()
|
template0 = read_conf("nginx.conf")
|
||||||
template1 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-alldomains.conf")).read()
|
template1 = read_conf("nginx-alldomains.conf")
|
||||||
template2 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-primaryonly.conf")).read()
|
template2 = read_conf("nginx-primaryonly.conf")
|
||||||
template3 = "\trewrite ^(.*) https://$REDIRECT_DOMAIN$1 permanent;\n"
|
template3 = "\trewrite ^(.*) https://$REDIRECT_DOMAIN$1 permanent;\n"
|
||||||
|
|
||||||
# Add the PRIMARY_HOST configuration first so it becomes nginx's default server.
|
# Add the PRIMARY_HOST configuration first so it becomes nginx's default server.
|
||||||
@@ -141,11 +147,8 @@ def make_domain_config(domain, templates, ssl_certificates, env):
|
|||||||
def hashfile(filepath):
|
def hashfile(filepath):
|
||||||
import hashlib
|
import hashlib
|
||||||
sha1 = hashlib.sha1()
|
sha1 = hashlib.sha1()
|
||||||
f = open(filepath, 'rb')
|
with open(filepath, 'rb') as f:
|
||||||
try:
|
|
||||||
sha1.update(f.read())
|
sha1.update(f.read())
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
return sha1.hexdigest()
|
return sha1.hexdigest()
|
||||||
nginx_conf_extra += "\t# ssl files sha1: %s / %s\n" % (hashfile(tls_cert["private-key"]), hashfile(tls_cert["certificate"]))
|
nginx_conf_extra += "\t# ssl files sha1: %s / %s\n" % (hashfile(tls_cert["private-key"]), hashfile(tls_cert["certificate"]))
|
||||||
|
|
||||||
@@ -153,7 +156,8 @@ def make_domain_config(domain, templates, ssl_certificates, env):
|
|||||||
hsts = "yes"
|
hsts = "yes"
|
||||||
nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
|
nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
|
||||||
if os.path.exists(nginx_conf_custom_fn):
|
if os.path.exists(nginx_conf_custom_fn):
|
||||||
yaml = rtyaml.load(open(nginx_conf_custom_fn))
|
with open(nginx_conf_custom_fn, 'r') as f:
|
||||||
|
yaml = rtyaml.load(f)
|
||||||
if domain in yaml:
|
if domain in yaml:
|
||||||
yaml = yaml[domain]
|
yaml = yaml[domain]
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Mail-in-a-Box Security Guide
|
Mail-in-a-Box Security Guide
|
||||||
============================
|
============================
|
||||||
|
|
||||||
Mail-in-a-Box turns a fresh Ubuntu 18.04 LTS 64-bit machine into a mail server appliance by installing and configuring various components.
|
Mail-in-a-Box turns a fresh Ubuntu 22.04 LTS 64-bit machine into a mail server appliance by installing and configuring various components.
|
||||||
|
|
||||||
This page documents the security posture of Mail-in-a-Box. The term “box” is used below to mean a configured Mail-in-a-Box.
|
This page documents the security posture of Mail-in-a-Box. The term “box” is used below to mean a configured Mail-in-a-Box.
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ if [ -z "$TAG" ]; then
|
|||||||
# want to display in status checks.
|
# want to display in status checks.
|
||||||
#
|
#
|
||||||
# Allow point-release versions of the major releases, e.g. 22.04.1 is OK.
|
# Allow point-release versions of the major releases, e.g. 22.04.1 is OK.
|
||||||
UBUNTU_VERSION=$( lsb_release -d | sed 's/.*:\s*//' | sed 's/\([0-9]*\.[0-9]*\)\.[0-9]/\1/' )"
|
UBUNTU_VERSION=$( lsb_release -d | sed 's/.*:\s*//' | sed 's/\([0-9]*\.[0-9]*\)\.[0-9]/\1/' )
|
||||||
if [ "$UBUNTU_VERSION" == "Ubuntu 22.04 LTS" ]; then
|
if [ "$UBUNTU_VERSION" == "Ubuntu 22.04 LTS" ]; then
|
||||||
# This machine is running Ubuntu 22.04, which is supported by
|
# This machine is running Ubuntu 22.04, which is supported by
|
||||||
# Mail-in-a-Box versions 60 and later.
|
# Mail-in-a-Box versions 60 and later.
|
||||||
TAG=v60
|
TAG=v66
|
||||||
elif [ "$UBUNTU_VERSION" == "Ubuntu 18.04 LTS" ]; then
|
elif [ "$UBUNTU_VERSION" == "Ubuntu 18.04 LTS" ]; then
|
||||||
# This machine is running Ubuntu 18.04, which is supported by
|
# This machine is running Ubuntu 18.04, which is supported by
|
||||||
# Mail-in-a-Box versions 0.40 through 5x.
|
# Mail-in-a-Box versions 0.40 through 5x.
|
||||||
@@ -51,33 +51,37 @@ if [[ $EUID -ne 0 ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Clone the Mail-in-a-Box repository if it doesn't exist.
|
# 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
|
if [ ! -f /usr/bin/git ]; then
|
||||||
echo Installing git . . .
|
echo "Installing git . . ."
|
||||||
apt-get -q -q update
|
apt-get -q -q update
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get -q -q install -y git < /dev/null
|
DEBIAN_FRONTEND=noninteractive apt-get -q -q install -y git < /dev/null
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo Downloading Mail-in-a-Box $TAG. . .
|
if [ "$SOURCE" == "" ]; then
|
||||||
|
SOURCE=https://github.com/mail-in-a-box/mailinabox
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Downloading Mail-in-a-Box $TAG. . ."
|
||||||
git clone \
|
git clone \
|
||||||
-b $TAG --depth 1 \
|
-b "$TAG" --depth 1 \
|
||||||
https://github.com/mail-in-a-box/mailinabox \
|
"$SOURCE" \
|
||||||
$HOME/mailinabox \
|
"$HOME/mailinabox" \
|
||||||
< /dev/null 2> /dev/null
|
< /dev/null 2> /dev/null
|
||||||
|
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Change directory to it.
|
# Change directory to it.
|
||||||
cd $HOME/mailinabox
|
cd "$HOME/mailinabox" || exit
|
||||||
|
|
||||||
# Update it.
|
# Update it.
|
||||||
if [ "$TAG" != $(git describe) ]; then
|
if [ "$TAG" != "$(git describe --always)" ]; then
|
||||||
echo Updating Mail-in-a-Box to $TAG . . .
|
echo "Updating Mail-in-a-Box to $TAG . . ."
|
||||||
git fetch --depth 1 --force --prune origin tag $TAG
|
git fetch --depth 1 --force --prune origin tag "$TAG"
|
||||||
if ! git checkout -q $TAG; then
|
if ! git checkout -q "$TAG"; then
|
||||||
echo "Update failed. Did you modify something in $(pwd)?"
|
echo "Update failed. Did you modify something in $PWD?"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ source setup/functions.sh # load our functions
|
|||||||
source /etc/mailinabox.conf # load global vars
|
source /etc/mailinabox.conf # load global vars
|
||||||
|
|
||||||
# Install DKIM...
|
# Install DKIM...
|
||||||
echo Installing OpenDKIM/OpenDMARC...
|
echo "Installing OpenDKIM/OpenDMARC..."
|
||||||
apt_install opendkim opendkim-tools opendmarc
|
apt_install opendkim opendkim-tools opendmarc
|
||||||
|
|
||||||
# Make sure configuration directories exist.
|
# Make sure configuration directories exist.
|
||||||
mkdir -p /etc/opendkim;
|
mkdir -p /etc/opendkim;
|
||||||
mkdir -p $STORAGE_ROOT/mail/dkim
|
mkdir -p "$STORAGE_ROOT/mail/dkim"
|
||||||
|
|
||||||
# Used in InternalHosts and ExternalIgnoreList configuration directives.
|
# Used in InternalHosts and ExternalIgnoreList configuration directives.
|
||||||
# Not quite sure why.
|
# Not quite sure why.
|
||||||
@@ -53,17 +53,17 @@ fi
|
|||||||
# such as Google. But they and others use a 2048 bit key, so we'll
|
# 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.
|
# do the same. Keys beyond 2048 bits may exceed DNS record limits.
|
||||||
if [ ! -f "$STORAGE_ROOT/mail/dkim/mail.private" ]; then
|
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
|
fi
|
||||||
|
|
||||||
# Ensure files are owned by the opendkim user and are private otherwise.
|
# Ensure files are owned by the opendkim user and are private otherwise.
|
||||||
chown -R opendkim:opendkim $STORAGE_ROOT/mail/dkim
|
chown -R opendkim:opendkim "$STORAGE_ROOT/mail/dkim"
|
||||||
chmod go-rwx $STORAGE_ROOT/mail/dkim
|
chmod go-rwx "$STORAGE_ROOT/mail/dkim"
|
||||||
|
|
||||||
tools/editconf.py /etc/opendmarc.conf -s \
|
tools/editconf.py /etc/opendmarc.conf -s \
|
||||||
"Syslog=true" \
|
"Syslog=true" \
|
||||||
"Socket=inet:8893@[127.0.0.1]" \
|
"Socket=inet:8893@[127.0.0.1]" \
|
||||||
"FailureReports=true"
|
"FailureReports=false"
|
||||||
|
|
||||||
# SPFIgnoreResults causes the filter to ignore any SPF results in the header
|
# SPFIgnoreResults causes the filter to ignore any SPF results in the header
|
||||||
# of the message. This is useful if you want the filter to perfrom SPF checks
|
# of the message. This is useful if you want the filter to perfrom SPF checks
|
||||||
@@ -82,11 +82,11 @@ tools/editconf.py /etc/opendmarc.conf -s \
|
|||||||
tools/editconf.py /etc/opendmarc.conf -s \
|
tools/editconf.py /etc/opendmarc.conf -s \
|
||||||
"SPFSelfValidate=true"
|
"SPFSelfValidate=true"
|
||||||
|
|
||||||
# Enables generation of failure reports for sending domains that publish a
|
# Disables generation of failure reports for sending domains that publish a
|
||||||
# "none" policy.
|
# "none" policy.
|
||||||
|
|
||||||
tools/editconf.py /etc/opendmarc.conf -s \
|
tools/editconf.py /etc/opendmarc.conf -s \
|
||||||
"FailureReportsOnNone=true"
|
"FailureReportsOnNone=false"
|
||||||
|
|
||||||
# AlwaysAddARHeader Adds an "Authentication-Results:" header field even to
|
# AlwaysAddARHeader Adds an "Authentication-Results:" header field even to
|
||||||
# unsigned messages from domains with no "signs all" policy. The reported DKIM
|
# unsigned messages from domains with no "signs all" policy. The reported DKIM
|
||||||
|
|||||||
@@ -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
|
# (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
|
# for non-RSA keys, so it's removed. The RSA-based keys are not recommended
|
||||||
# anymore anyway.)
|
# 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
|
# Now create a Zone-Signing Key (ZSK) which is expected to be
|
||||||
# rotated more often than a KSK, although we have no plans to
|
# 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`.
|
# disturbing DNS availability.) Omit `-k`.
|
||||||
# (This previously used -b 1024 but it's unclear if this setting makes sense
|
# (This previously used -b 1024 but it's unclear if this setting makes sense
|
||||||
# for non-RSA keys, so it's removed.)
|
# 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:
|
# 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.
|
# options. So we'll store the names of the files we just generated.
|
||||||
# We might have multiple keys down the road. This will identify
|
# We might have multiple keys down the road. This will identify
|
||||||
# what keys are the current keys.
|
# what keys are the current keys.
|
||||||
cat > $STORAGE_ROOT/dns/dnssec/$algo.conf << EOF;
|
cat > "$STORAGE_ROOT/dns/dnssec/$algo.conf" << EOF;
|
||||||
KSK=$KSK
|
KSK=$KSK
|
||||||
ZSK=$ZSK
|
ZSK=$ZSK
|
||||||
EOF
|
EOF
|
||||||
@@ -142,7 +142,7 @@ cat > /etc/cron.daily/mailinabox-dnssec << EOF;
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Mail-in-a-Box
|
# Mail-in-a-Box
|
||||||
# Re-sign any DNS zones with DNSSEC because the signatures expire periodically.
|
# Re-sign any DNS zones with DNSSEC because the signatures expire periodically.
|
||||||
$(pwd)/tools/dns_update
|
$PWD/tools/dns_update
|
||||||
EOF
|
EOF
|
||||||
chmod +x /etc/cron.daily/mailinabox-dnssec
|
chmod +x /etc/cron.daily/mailinabox-dnssec
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
# If there aren't any mail users yet, create one.
|
# If there aren't any mail users yet, create one.
|
||||||
if [ -z "$(management/cli.py user)" ]; then
|
if [ -z "$(management/cli.py user)" ]; then
|
||||||
# The outut of "management/cli.py user" is a list of mail users. If there
|
# 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" \
|
input_box "Mail Account" \
|
||||||
"Let's create your first mail account.
|
"Let's create your first mail account.
|
||||||
\n\nWhat email address do you want?" \
|
\n\nWhat email address do you want?" \
|
||||||
me@$(get_default_hostname) \
|
"me@$(get_default_hostname)" \
|
||||||
EMAIL_ADDR
|
EMAIL_ADDR
|
||||||
|
|
||||||
if [ -z "$EMAIL_ADDR" ]; then
|
if [ -z "$EMAIL_ADDR" ]; then
|
||||||
@@ -22,7 +23,7 @@ if [ -z "$(management/cli.py user)" ]; then
|
|||||||
input_box "Mail Account" \
|
input_box "Mail Account" \
|
||||||
"That's not a valid email address.
|
"That's not a valid email address.
|
||||||
\n\nWhat email address do you want?" \
|
\n\nWhat email address do you want?" \
|
||||||
$EMAIL_ADDR \
|
"$EMAIL_ADDR" \
|
||||||
EMAIL_ADDR
|
EMAIL_ADDR
|
||||||
if [ -z "$EMAIL_ADDR" ]; then
|
if [ -z "$EMAIL_ADDR" ]; then
|
||||||
# user hit ESC/cancel
|
# user hit ESC/cancel
|
||||||
@@ -47,11 +48,11 @@ if [ -z "$(management/cli.py user)" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Create the user's mail account. This will ask for a password if none was given above.
|
# 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.
|
# 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.
|
# 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
|
fi
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
# Turn on "strict mode." See http://redsymbol.net/articles/unofficial-bash-strict-mode/.
|
# Turn on "strict mode." See http://redsymbol.net/articles/unofficial-bash-strict-mode/.
|
||||||
# -e: exit if any command unexpectedly fails.
|
# -e: exit if any command unexpectedly fails.
|
||||||
# -u: exit if we have a variable typo.
|
# -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
|
# Execute command, redirecting stderr/stdout to the temporary file. Since we
|
||||||
# check the return code ourselves, disable 'set -e' temporarily.
|
# check the return code ourselves, disable 'set -e' temporarily.
|
||||||
set +e
|
set +e
|
||||||
"$@" &> $OUTPUT
|
"$@" &> "$OUTPUT"
|
||||||
E=$?
|
E=$?
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
@@ -24,15 +25,15 @@ function hide_output {
|
|||||||
if [ $E != 0 ]; then
|
if [ $E != 0 ]; then
|
||||||
# Something failed.
|
# Something failed.
|
||||||
echo
|
echo
|
||||||
echo FAILED: "$@"
|
echo "FAILED: $*"
|
||||||
echo -----------------------------------------
|
echo -----------------------------------------
|
||||||
cat $OUTPUT
|
cat "$OUTPUT"
|
||||||
echo -----------------------------------------
|
echo -----------------------------------------
|
||||||
exit $E
|
exit $E
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove temporary file.
|
# Remove temporary file.
|
||||||
rm -f $OUTPUT
|
rm -f "$OUTPUT"
|
||||||
}
|
}
|
||||||
|
|
||||||
function apt_get_quiet {
|
function apt_get_quiet {
|
||||||
@@ -62,9 +63,9 @@ function get_default_hostname {
|
|||||||
# Guess the machine's hostname. It should be a fully qualified
|
# Guess the machine's hostname. It should be a fully qualified
|
||||||
# domain name suitable for DNS. None of these calls may provide
|
# domain name suitable for DNS. None of these calls may provide
|
||||||
# the right value, but it's the best guess we can make.
|
# 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 --all-fqdns 2>/dev/null ||
|
||||||
hostname 2>/dev/null)
|
hostname 2>/dev/null)"
|
||||||
printf '%s\n' "$1" # return this value
|
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
|
# Pass '4' or '6' as an argument to this function to specify
|
||||||
# what type of address to get (IPv4, IPv6).
|
# 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 {
|
function get_default_privateip {
|
||||||
@@ -119,19 +120,19 @@ function get_default_privateip {
|
|||||||
if [ "$1" == "6" ]; then target=2001:4860:4860::8888; fi
|
if [ "$1" == "6" ]; then target=2001:4860:4860::8888; fi
|
||||||
|
|
||||||
# Get the route information.
|
# 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.
|
# 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
|
if [[ "$1" == "6" && $address == fe80:* ]]; then
|
||||||
# For IPv6 link-local addresses, parse the interface out
|
# For IPv6 link-local addresses, parse the interface out
|
||||||
# of the route information and append it with a '%'.
|
# 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
|
address=$address%$interface
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo $address
|
echo "$address"
|
||||||
}
|
}
|
||||||
|
|
||||||
function ufw_allow {
|
function ufw_allow {
|
||||||
@@ -149,7 +150,7 @@ function ufw_limit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function restart_service {
|
function restart_service {
|
||||||
hide_output service $1 restart
|
hide_output service "$1" restart
|
||||||
}
|
}
|
||||||
|
|
||||||
## Dialog Functions ##
|
## Dialog Functions ##
|
||||||
@@ -178,7 +179,7 @@ function input_menu {
|
|||||||
declare -n result_code=$4_EXITCODE
|
declare -n result_code=$4_EXITCODE
|
||||||
local IFS=^$'\n'
|
local IFS=^$'\n'
|
||||||
set +e
|
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=$?
|
result_code=$?
|
||||||
set -e
|
set -e
|
||||||
}
|
}
|
||||||
@@ -190,17 +191,17 @@ function wget_verify {
|
|||||||
HASH=$2
|
HASH=$2
|
||||||
DEST=$3
|
DEST=$3
|
||||||
CHECKSUM="$HASH $DEST"
|
CHECKSUM="$HASH $DEST"
|
||||||
rm -f $DEST
|
rm -f "$DEST"
|
||||||
hide_output wget -O $DEST $URL
|
hide_output wget -O "$DEST" "$URL"
|
||||||
if ! echo "$CHECKSUM" | sha1sum --check --strict > /dev/null; then
|
if ! echo "$CHECKSUM" | sha1sum --check --strict > /dev/null; then
|
||||||
echo "------------------------------------------------------------"
|
echo "------------------------------------------------------------"
|
||||||
echo "Download of $URL did not match expected checksum."
|
echo "Download of $URL did not match expected checksum."
|
||||||
echo "Found:"
|
echo "Found:"
|
||||||
sha1sum $DEST
|
sha1sum "$DEST"
|
||||||
echo
|
echo
|
||||||
echo "Expected:"
|
echo "Expected:"
|
||||||
echo "$CHECKSUM"
|
echo "$CHECKSUM"
|
||||||
rm -f $DEST
|
rm -f "$DEST"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -216,9 +217,9 @@ function git_clone {
|
|||||||
SUBDIR=$3
|
SUBDIR=$3
|
||||||
TARGETPATH=$4
|
TARGETPATH=$4
|
||||||
TMPPATH=/tmp/git-clone-$$
|
TMPPATH=/tmp/git-clone-$$
|
||||||
rm -rf $TMPPATH $TARGETPATH
|
rm -rf $TMPPATH "$TARGETPATH"
|
||||||
git clone -q $REPO $TMPPATH || exit 1
|
git clone -q "$REPO" $TMPPATH || exit 1
|
||||||
(cd $TMPPATH; git checkout -q $TREEISH;) || exit 1
|
(cd $TMPPATH; git checkout -q "$TREEISH";) || exit 1
|
||||||
mv $TMPPATH/$SUBDIR $TARGETPATH
|
mv $TMPPATH/"$SUBDIR" "$TARGETPATH"
|
||||||
rm -rf $TMPPATH
|
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/2012-August/137569.html
|
||||||
# - https://www.dovecot.org/list/dovecot/2011-December/132455.html
|
# - https://www.dovecot.org/list/dovecot/2011-December/132455.html
|
||||||
tools/editconf.py /etc/dovecot/conf.d/10-master.conf \
|
tools/editconf.py /etc/dovecot/conf.d/10-master.conf \
|
||||||
default_process_limit=$(echo "$(nproc) * 250" | bc) \
|
default_process_limit="$(($(nproc) * 250))" \
|
||||||
default_vsz_limit=$(echo "$(free -tm | tail -1 | awk '{print $2}') / 3" | bc)M \
|
default_vsz_limit="$(($(free -tm | tail -1 | awk '{print $2}') / 3))M" \
|
||||||
log_path=/var/log/mail.log
|
log_path=/var/log/mail.log
|
||||||
|
|
||||||
# The inotify `max_user_instances` default is 128, which constrains
|
# The inotify `max_user_instances` default is 128, which constrains
|
||||||
@@ -61,7 +61,7 @@ tools/editconf.py /etc/sysctl.conf \
|
|||||||
# username part of the user's email address. We'll ensure that no bad domains or email addresses
|
# username part of the user's email address. We'll ensure that no bad domains or email addresses
|
||||||
# are created within the management daemon.
|
# are created within the management daemon.
|
||||||
tools/editconf.py /etc/dovecot/conf.d/10-mail.conf \
|
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 \
|
mail_privileged_group=mail \
|
||||||
first_valid_uid=0
|
first_valid_uid=0
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ EOF
|
|||||||
# Setting a `postmaster_address` is required or LMTP won't start. An alias
|
# Setting a `postmaster_address` is required or LMTP won't start. An alias
|
||||||
# will be created automatically by our management daemon.
|
# will be created automatically by our management daemon.
|
||||||
tools/editconf.py /etc/dovecot/conf.d/15-lda.conf \
|
tools/editconf.py /etc/dovecot/conf.d/15-lda.conf \
|
||||||
postmaster_address=postmaster@$PRIMARY_HOSTNAME
|
"postmaster_address=postmaster@$PRIMARY_HOSTNAME"
|
||||||
|
|
||||||
# ### Sieve
|
# ### Sieve
|
||||||
|
|
||||||
@@ -201,14 +201,14 @@ chown -R mail:dovecot /etc/dovecot
|
|||||||
chmod -R o-rwx /etc/dovecot
|
chmod -R o-rwx /etc/dovecot
|
||||||
|
|
||||||
# Ensure mailbox files have a directory that exists and are owned by the mail user.
|
# Ensure mailbox files have a directory that exists and are owned by the mail user.
|
||||||
mkdir -p $STORAGE_ROOT/mail/mailboxes
|
mkdir -p "$STORAGE_ROOT/mail/mailboxes"
|
||||||
chown -R mail.mail $STORAGE_ROOT/mail/mailboxes
|
chown -R mail:mail "$STORAGE_ROOT/mail/mailboxes"
|
||||||
|
|
||||||
# Same for the sieve scripts.
|
# Same for the sieve scripts.
|
||||||
mkdir -p $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_before"
|
||||||
mkdir -p $STORAGE_ROOT/mail/sieve/global_after
|
mkdir -p "$STORAGE_ROOT/mail/sieve/global_after"
|
||||||
chown -R mail.mail $STORAGE_ROOT/mail/sieve
|
chown -R mail:mail "$STORAGE_ROOT/mail/sieve"
|
||||||
|
|
||||||
# Allow the IMAP/POP ports in the firewall.
|
# Allow the IMAP/POP ports in the firewall.
|
||||||
ufw_allow imaps
|
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).
|
# * Set the SMTP banner (which must have the hostname first, then anything).
|
||||||
tools/editconf.py /etc/postfix/main.cf \
|
tools/editconf.py /etc/postfix/main.cf \
|
||||||
inet_interfaces=all \
|
inet_interfaces=all \
|
||||||
smtp_bind_address=$PRIVATE_IP \
|
smtp_bind_address="$PRIVATE_IP" \
|
||||||
smtp_bind_address6=$PRIVATE_IPV6 \
|
smtp_bind_address6="$PRIVATE_IPV6" \
|
||||||
myhostname=$PRIMARY_HOSTNAME\
|
myhostname="$PRIMARY_HOSTNAME"\
|
||||||
smtpd_banner="\$myhostname ESMTP Hi, I'm a Mail-in-a-Box (Ubuntu/Postfix; see https://mailinabox.email/)" \
|
smtpd_banner="\$myhostname ESMTP Hi, I'm a Mail-in-a-Box (Ubuntu/Postfix; see https://mailinabox.email/)" \
|
||||||
mydestination=localhost
|
mydestination=localhost
|
||||||
|
|
||||||
@@ -126,9 +126,9 @@ sed -i "s/PUBLIC_IP/$PUBLIC_IP/" /etc/postfix/outgoing_mail_header_filters
|
|||||||
tools/editconf.py /etc/postfix/main.cf \
|
tools/editconf.py /etc/postfix/main.cf \
|
||||||
smtpd_tls_security_level=may\
|
smtpd_tls_security_level=may\
|
||||||
smtpd_tls_auth_only=yes \
|
smtpd_tls_auth_only=yes \
|
||||||
smtpd_tls_cert_file=$STORAGE_ROOT/ssl/ssl_certificate.pem \
|
smtpd_tls_cert_file="$STORAGE_ROOT/ssl/ssl_certificate.pem" \
|
||||||
smtpd_tls_key_file=$STORAGE_ROOT/ssl/ssl_private_key.pem \
|
smtpd_tls_key_file="$STORAGE_ROOT/ssl/ssl_private_key.pem" \
|
||||||
smtpd_tls_dh1024_param_file=$STORAGE_ROOT/ssl/dh2048.pem \
|
smtpd_tls_dh1024_param_file="$STORAGE_ROOT/ssl/dh2048.pem" \
|
||||||
smtpd_tls_protocols="!SSLv2,!SSLv3" \
|
smtpd_tls_protocols="!SSLv2,!SSLv3" \
|
||||||
smtpd_tls_ciphers=medium \
|
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 \
|
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 \
|
||||||
@@ -225,7 +225,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
|
# "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 \
|
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" \
|
smtpd_sender_restrictions="reject_non_fqdn_sender,reject_unknown_sender_domain,reject_authenticated_sender_login_mismatch,reject_rhsbl_sender dbl.spamhaus.org" \
|
||||||
smtpd_recipient_restrictions=permit_sasl_authenticated,permit_mynetworks,"reject_rbl_client zen.spamhaus.org",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,reject_unlisted_recipient,check_policy_service inet:127.0.0.1:10023"
|
||||||
|
|
||||||
# Postfix connects to Postgrey on the 127.0.0.1 interface specifically. Ensure that
|
# 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).
|
# Postgrey listens on the same interface (and not IPv6, for instance).
|
||||||
@@ -247,17 +247,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 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
|
# Stop the service
|
||||||
service postgrey stop
|
service postgrey stop
|
||||||
# Ensure the new paths for postgrey db exists
|
# 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
|
# 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
|
fi
|
||||||
# Ensure permissions are set
|
# Ensure permissions are set
|
||||||
chown -R postgrey:postgrey $STORAGE_ROOT/mail/postgrey/
|
chown -R postgrey:postgrey "$STORAGE_ROOT/mail/postgrey/"
|
||||||
chmod 700 $STORAGE_ROOT/mail/postgrey/{,db}
|
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
|
# 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;
|
cat > /etc/cron.daily/mailinabox-postgrey-whitelist << EOF;
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ source /etc/mailinabox.conf # load global vars
|
|||||||
db_path=$STORAGE_ROOT/mail/users.sqlite
|
db_path=$STORAGE_ROOT/mail/users.sqlite
|
||||||
|
|
||||||
# Create an empty database if it doesn't yet exist.
|
# Create an empty database if it doesn't yet exist.
|
||||||
if [ ! -f $db_path ]; then
|
if [ ! -f "$db_path" ]; then
|
||||||
echo Creating new user database: $db_path;
|
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 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 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 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;
|
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";
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ### User Authentication
|
# ### User Authentication
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ inst_dir=/usr/local/lib/mailinabox
|
|||||||
mkdir -p $inst_dir
|
mkdir -p $inst_dir
|
||||||
venv=$inst_dir/env
|
venv=$inst_dir/env
|
||||||
if [ ! -d $venv ]; then
|
if [ ! -d $venv ]; then
|
||||||
|
# A bug specific to Ubuntu 22.04 and Python 3.10 requires
|
||||||
|
# forcing a virtualenv directory layout option (see #2335
|
||||||
|
# and https://github.com/pypa/virtualenv/pull/2415). In
|
||||||
|
# our issue, reportedly installing python3-distutils didn't
|
||||||
|
# fix the problem.)
|
||||||
|
export DEB_PYTHON_INSTALL_LAYOUT='deb'
|
||||||
hide_output virtualenv -ppython3 $venv
|
hide_output virtualenv -ppython3 $venv
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -46,9 +52,9 @@ hide_output $venv/bin/pip install --upgrade \
|
|||||||
# CONFIGURATION
|
# CONFIGURATION
|
||||||
|
|
||||||
# Create a backup directory and a random key for encrypting backups.
|
# Create a backup directory and a random key for encrypting backups.
|
||||||
mkdir -p $STORAGE_ROOT/backup
|
mkdir -p "$STORAGE_ROOT/backup"
|
||||||
if [ ! -f $STORAGE_ROOT/backup/secret_key.txt ]; then
|
if [ ! -f "$STORAGE_ROOT/backup/secret_key.txt" ]; then
|
||||||
$(umask 077; openssl rand -base64 2048 > $STORAGE_ROOT/backup/secret_key.txt)
|
umask 077; openssl rand -base64 2048 > "$STORAGE_ROOT/backup/secret_key.txt"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
@@ -94,7 +100,7 @@ tr -cd '[:xdigit:]' < /dev/urandom | head -c 32 > /var/lib/mailinabox/api.key
|
|||||||
chmod 640 /var/lib/mailinabox/api.key
|
chmod 640 /var/lib/mailinabox/api.key
|
||||||
|
|
||||||
source $venv/bin/activate
|
source $venv/bin/activate
|
||||||
export PYTHONPATH=$(pwd)/management
|
export PYTHONPATH=$PWD/management
|
||||||
exec gunicorn -b localhost:10222 -w 1 --timeout 630 wsgi:app
|
exec gunicorn -b localhost:10222 -w 1 --timeout 630 wsgi:app
|
||||||
EOF
|
EOF
|
||||||
chmod +x $inst_dir/start
|
chmod +x $inst_dir/start
|
||||||
@@ -110,7 +116,7 @@ minute=$((RANDOM % 60)) # avoid overloading mailinabox.email
|
|||||||
cat > /etc/cron.d/mailinabox-nightly << EOF;
|
cat > /etc/cron.d/mailinabox-nightly << EOF;
|
||||||
# Mail-in-a-Box --- Do not edit / will be overwritten on update.
|
# Mail-in-a-Box --- Do not edit / will be overwritten on update.
|
||||||
# Run nightly tasks: backup, status checks.
|
# 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
|
EOF
|
||||||
|
|
||||||
# Start the management server.
|
# Start the management server.
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ contact.admin.always_send warning critical
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
# The Debian installer touches these files and chowns them to www-data:adm for use with spawn-fcgi
|
# The Debian installer touches these files and chowns them to www-data:adm for use with spawn-fcgi
|
||||||
chown munin. /var/log/munin/munin-cgi-html.log
|
chown munin /var/log/munin/munin-cgi-html.log
|
||||||
chown munin. /var/log/munin/munin-cgi-graph.log
|
chown munin /var/log/munin/munin-cgi-graph.log
|
||||||
|
|
||||||
# ensure munin-node knows the name of this machine
|
# ensure munin-node knows the name of this machine
|
||||||
# and reduce logging level to warning
|
# and reduce logging level to warning
|
||||||
tools/editconf.py /etc/munin/munin-node.conf -s \
|
tools/editconf.py /etc/munin/munin-node.conf -s \
|
||||||
host_name=$PRIMARY_HOSTNAME \
|
host_name="$PRIMARY_HOSTNAME" \
|
||||||
log_level=1
|
log_level=1
|
||||||
|
|
||||||
# Update the activated plugins through munin's autoconfiguration.
|
# 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.
|
# 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
|
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=$(echo "$f" | sed s/.*_//);
|
||||||
if ! grep -qFx up /sys/class/net/$IF/operstate 2>/dev/null; then
|
if ! grep -qFx up "/sys/class/net/$IF/operstate" 2>/dev/null; then
|
||||||
rm $f;
|
rm "$f";
|
||||||
fi;
|
fi;
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ done
|
|||||||
mkdir -p /var/lib/munin-node/plugin-state/
|
mkdir -p /var/lib/munin-node/plugin-state/
|
||||||
|
|
||||||
# Create a systemd service for munin.
|
# 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
|
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
|
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
|
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
|
# 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.
|
# the rest of the system setup so we may not yet have things installed.
|
||||||
apt_get_quiet install bind9-host sed netcat-openbsd
|
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
|
# 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
|
# and will not be able to reliably send mail. Do this after any automatic
|
||||||
# choices made above.
|
# 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
|
||||||
echo "The hostname you chose '$PRIMARY_HOSTNAME' is listed in the"
|
echo "The hostname you chose '$PRIMARY_HOSTNAME' is listed in the"
|
||||||
echo "Spamhaus Domain Block List. See http://www.spamhaus.org/dbl/"
|
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
|
# 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
|
# 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.
|
# 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/")
|
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
|
if host "$REVERSED_IPV4.zen.spamhaus.org" > /dev/null; then
|
||||||
echo
|
echo
|
||||||
echo "The IP address $PUBLIC_IP is listed in the Spamhaus Block List."
|
echo "The IP address $PUBLIC_IP is listed in the Spamhaus Block List."
|
||||||
echo "See http://www.spamhaus.org/query/ip/$PUBLIC_IP."
|
echo "See http://www.spamhaus.org/query/ip/$PUBLIC_IP."
|
||||||
|
|||||||
@@ -21,36 +21,40 @@ echo "Installing Nextcloud (contacts/calendar)..."
|
|||||||
# we automatically install intermediate versions as needed.
|
# we automatically install intermediate versions as needed.
|
||||||
# * The hash is the SHA1 hash of the ZIP package, which you can find by just running this script and
|
# * The hash is the SHA1 hash of the ZIP package, which you can find by just running this script and
|
||||||
# copying it from the error message when it doesn't match what is below.
|
# copying it from the error message when it doesn't match what is below.
|
||||||
nextcloud_ver=23.0.8
|
nextcloud_ver=25.0.7
|
||||||
nextcloud_hash=9d63416a0697eeecf243d09461f5881f8997f50a
|
nextcloud_hash=a5a565c916355005c7b408dd41a1e53505e1a080
|
||||||
|
|
||||||
# Nextcloud apps
|
# Nextcloud apps
|
||||||
# --------------
|
# --------------
|
||||||
# * Find the most recent tag that is compatible with the Nextcloud version above by
|
# * Find the most recent tag that is compatible with the Nextcloud version above by
|
||||||
# consulting the <dependencies>...<nextcloud> node at:
|
# consulting the <dependencies>...<nextcloud> node at:
|
||||||
# https://github.com/nextcloud-releases/contacts/blob/master/appinfo/info.xml
|
# https://github.com/nextcloud-releases/contacts/blob/main/appinfo/info.xml
|
||||||
# https://github.com/nextcloud-releases/calendar/blob/master/appinfo/info.xml
|
# https://github.com/nextcloud-releases/calendar/blob/main/appinfo/info.xml
|
||||||
# https://github.com/nextcloud/user_external/blob/master/appinfo/info.xml
|
# https://github.com/nextcloud/user_external/blob/master/appinfo/info.xml
|
||||||
# * The hash is the SHA1 hash of the ZIP package, which you can find by just running this script and
|
# * The hash is the SHA1 hash of the ZIP package, which you can find by just running this script and
|
||||||
# copying it from the error message when it doesn't match what is below.
|
# copying it from the error message when it doesn't match what is below.
|
||||||
contacts_ver=4.2.0
|
contacts_ver=5.3.0
|
||||||
contacts_hash=79b506574834db5e1b6ab47aadd4041e12ad9a9c
|
contacts_hash=4b0a6666374e3b55cfd2ae9b72e1d458b87d4c8c
|
||||||
calendar_ver=3.5.0
|
|
||||||
calendar_hash=941381536287a015081669513f8f79f6f262508a
|
# Always ensure the versions are supported, see https://apps.nextcloud.com/apps/calendar
|
||||||
user_external_ver=3.0.0
|
calendar_ver=4.4.2
|
||||||
user_external_hash=0df781b261f55bbde73d8c92da3f99397000972f
|
calendar_hash=21a42e15806adc9b2618760ef94f1797ef399e2f
|
||||||
|
|
||||||
|
# And https://apps.nextcloud.com/apps/user_external
|
||||||
|
user_external_ver=3.2.0
|
||||||
|
user_external_hash=a494073dcdecbbbc79a9c77f72524ac9994d2eec
|
||||||
|
|
||||||
# Clear prior packages and install dependencies from apt.
|
# Clear prior packages and install dependencies from apt.
|
||||||
|
|
||||||
apt-get purge -qq -y owncloud* # we used to use the package manager
|
apt-get purge -qq -y owncloud* # we used to use the package manager
|
||||||
|
|
||||||
apt_install curl php${PHP_VER} php${PHP_VER}-fpm \
|
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}"-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}"-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
|
php"${PHP_VER}"-intl php"${PHP_VER}"-imagick php"${PHP_VER}"-gmp php"${PHP_VER}"-bcmath
|
||||||
|
|
||||||
# Enable APC before Nextcloud tools are run.
|
# 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.enabled=1 \
|
||||||
apc.enable_cli=1
|
apc.enable_cli=1
|
||||||
|
|
||||||
@@ -69,8 +73,8 @@ InstallNextcloud() {
|
|||||||
echo "Upgrading to Nextcloud version $version"
|
echo "Upgrading to Nextcloud version $version"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# Download and verify
|
# 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
|
# Remove the current owncloud/Nextcloud
|
||||||
rm -rf /usr/local/lib/owncloud
|
rm -rf /usr/local/lib/owncloud
|
||||||
@@ -84,18 +88,18 @@ InstallNextcloud() {
|
|||||||
# their github repositories.
|
# their github repositories.
|
||||||
mkdir -p /usr/local/lib/owncloud/apps
|
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/
|
tar xf /tmp/contacts.tgz -C /usr/local/lib/owncloud/apps/
|
||||||
rm /tmp/contacts.tgz
|
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/
|
tar xf /tmp/calendar.tgz -C /usr/local/lib/owncloud/apps/
|
||||||
rm /tmp/calendar.tgz
|
rm /tmp/calendar.tgz
|
||||||
|
|
||||||
# Starting with Nextcloud 15, the app user_external is no longer included in Nextcloud core,
|
# Starting with Nextcloud 15, the app user_external is no longer included in Nextcloud core,
|
||||||
# we will install from their github repository.
|
# we will install from their github repository.
|
||||||
if [ -n "$version_user_external" ]; then
|
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/
|
tar -xf /tmp/user_external.tgz -C /usr/local/lib/owncloud/apps/
|
||||||
rm /tmp/user_external.tgz
|
rm /tmp/user_external.tgz
|
||||||
fi
|
fi
|
||||||
@@ -105,32 +109,35 @@ InstallNextcloud() {
|
|||||||
|
|
||||||
# Create a symlink to the config.php in STORAGE_ROOT (for upgrades we're restoring the symlink we previously
|
# 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).
|
# 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.
|
# Make sure permissions are correct or the upgrade step won't run.
|
||||||
# $STORAGE_ROOT/owncloud may not yet exist, so use -f to suppress
|
# $STORAGE_ROOT/owncloud may not yet exist, so use -f to suppress
|
||||||
# that error.
|
# 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.
|
# 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).
|
# 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
|
# ownCloud 8.1.1 broke upgrades. It may fail on the first attempt, but
|
||||||
# that can be OK.
|
# that can be OK.
|
||||||
sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/occ upgrade
|
sudo -u www-data php"$PHP_VER" /usr/local/lib/owncloud/occ upgrade
|
||||||
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then
|
E=$?
|
||||||
|
if [ $E -ne 0 ] && [ $E -ne 3 ]; then
|
||||||
echo "Trying ownCloud upgrade again to work around ownCloud upgrade bug..."
|
echo "Trying ownCloud upgrade again to work around ownCloud upgrade bug..."
|
||||||
sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/occ upgrade
|
sudo -u www-data php"$PHP_VER" /usr/local/lib/owncloud/occ upgrade
|
||||||
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
|
E=$?
|
||||||
sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/occ maintenance:mode --off
|
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."
|
echo "...which seemed to work."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Add missing indices. NextCloud didn't include this in the normal upgrade because it might take some time.
|
# 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-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.
|
# 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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +149,7 @@ InstallNextcloud() {
|
|||||||
|
|
||||||
# If config.php exists, get version number, otherwise CURRENT_NEXTCLOUD_VER is empty.
|
# If config.php exists, get version number, otherwise CURRENT_NEXTCLOUD_VER is empty.
|
||||||
if [ -f "$STORAGE_ROOT/owncloud/config.php" ]; then
|
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
|
else
|
||||||
CURRENT_NEXTCLOUD_VER=""
|
CURRENT_NEXTCLOUD_VER=""
|
||||||
fi
|
fi
|
||||||
@@ -152,7 +159,7 @@ fi
|
|||||||
if [ ! -d /usr/local/lib/owncloud/ ] || [[ ! ${CURRENT_NEXTCLOUD_VER} =~ ^$nextcloud_ver ]]; then
|
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.
|
# 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.
|
# Backup the existing ownCloud/Nextcloud.
|
||||||
# Create a backup directory to store the current installation and database to
|
# Create a backup directory to store the current installation and database to
|
||||||
@@ -162,17 +169,23 @@ 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..."
|
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"
|
cp -r /usr/local/lib/owncloud "$BACKUP_DIRECTORY/owncloud-install"
|
||||||
fi
|
fi
|
||||||
if [ -e $STORAGE_ROOT/owncloud/owncloud.db ]; then
|
if [ -e "$STORAGE_ROOT/owncloud/owncloud.db" ]; then
|
||||||
cp $STORAGE_ROOT/owncloud/owncloud.db $BACKUP_DIRECTORY
|
cp "$STORAGE_ROOT/owncloud/owncloud.db" "$BACKUP_DIRECTORY"
|
||||||
fi
|
fi
|
||||||
if [ -e $STORAGE_ROOT/owncloud/config.php ]; then
|
if [ -e "$STORAGE_ROOT/owncloud/config.php" ]; then
|
||||||
cp $STORAGE_ROOT/owncloud/config.php $BACKUP_DIRECTORY
|
cp "$STORAGE_ROOT/owncloud/config.php" "$BACKUP_DIRECTORY"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# If ownCloud or Nextcloud was previously installed....
|
# 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
|
# Database migrations from ownCloud are no longer possible because ownCloud cannot be run under
|
||||||
# PHP 7.
|
# PHP 7.
|
||||||
|
|
||||||
|
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"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ ${CURRENT_NEXTCLOUD_VER} =~ ^[89] ]]; then
|
if [[ ${CURRENT_NEXTCLOUD_VER} =~ ^[89] ]]; then
|
||||||
echo "Upgrades from Mail-in-a-Box prior to v0.28 (dated July 30, 2018) with Nextcloud < 13.0.6 (you have ownCloud 8 or 9) are not supported. Upgrade to Mail-in-a-Box version v0.30 first. Setup will continue, but skip the Nextcloud migration."
|
echo "Upgrades from Mail-in-a-Box prior to v0.28 (dated July 30, 2018) with Nextcloud < 13.0.6 (you have ownCloud 8 or 9) are not supported. Upgrade to Mail-in-a-Box version v0.30 first. Setup will continue, but skip the Nextcloud migration."
|
||||||
return 0
|
return 0
|
||||||
@@ -183,6 +196,7 @@ if [ ! -d /usr/local/lib/owncloud/ ] || [[ ! ${CURRENT_NEXTCLOUD_VER} =~ ^$nextc
|
|||||||
echo "Upgrades from Mail-in-a-Box prior to v60 with Nextcloud 19 or earlier are not supported. Upgrade to the latest Mail-in-a-Box version supported on your machine first. Setup will continue, but skip the Nextcloud migration."
|
echo "Upgrades from Mail-in-a-Box prior to v60 with Nextcloud 19 or earlier are not supported. Upgrade to the latest Mail-in-a-Box version supported on your machine first. Setup will continue, but skip the Nextcloud migration."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${CURRENT_NEXTCLOUD_VER} =~ ^20 ]]; then
|
if [[ ${CURRENT_NEXTCLOUD_VER} =~ ^20 ]]; then
|
||||||
InstallNextcloud 21.0.7 f5c7079c5b56ce1e301c6a27c0d975d608bb01c9 4.0.7 45e7cf4bfe99cd8d03625cf9e5a1bb2e90549136 3.0.4 d0284b68135777ec9ca713c307216165b294d0fe
|
InstallNextcloud 21.0.7 f5c7079c5b56ce1e301c6a27c0d975d608bb01c9 4.0.7 45e7cf4bfe99cd8d03625cf9e5a1bb2e90549136 3.0.4 d0284b68135777ec9ca713c307216165b294d0fe
|
||||||
CURRENT_NEXTCLOUD_VER="21.0.7"
|
CURRENT_NEXTCLOUD_VER="21.0.7"
|
||||||
@@ -191,6 +205,14 @@ if [ ! -d /usr/local/lib/owncloud/ ] || [[ ! ${CURRENT_NEXTCLOUD_VER} =~ ^$nextc
|
|||||||
InstallNextcloud 22.2.6 9d39741f051a8da42ff7df46ceef2653a1dc70d9 4.1.0 697f6b4a664e928d72414ea2731cb2c9d1dc3077 3.2.2 ce4030ab57f523f33d5396c6a81396d440756f5f 3.0.0 0df781b261f55bbde73d8c92da3f99397000972f
|
InstallNextcloud 22.2.6 9d39741f051a8da42ff7df46ceef2653a1dc70d9 4.1.0 697f6b4a664e928d72414ea2731cb2c9d1dc3077 3.2.2 ce4030ab57f523f33d5396c6a81396d440756f5f 3.0.0 0df781b261f55bbde73d8c92da3f99397000972f
|
||||||
CURRENT_NEXTCLOUD_VER="22.2.6"
|
CURRENT_NEXTCLOUD_VER="22.2.6"
|
||||||
fi
|
fi
|
||||||
|
if [[ ${CURRENT_NEXTCLOUD_VER} =~ ^22 ]]; then
|
||||||
|
InstallNextcloud 23.0.12 d138641b8e7aabebe69bb3ec7c79a714d122f729 4.1.0 697f6b4a664e928d72414ea2731cb2c9d1dc3077 3.2.2 ce4030ab57f523f33d5396c6a81396d440756f5f 3.0.0 0df781b261f55bbde73d8c92da3f99397000972f
|
||||||
|
CURRENT_NEXTCLOUD_VER="23.0.12"
|
||||||
|
fi
|
||||||
|
if [[ ${CURRENT_NEXTCLOUD_VER} =~ ^23 ]]; then
|
||||||
|
InstallNextcloud 24.0.12 7aa5d61632c1ccf4ca3ff00fb6b295d318c05599 4.1.0 697f6b4a664e928d72414ea2731cb2c9d1dc3077 3.2.2 ce4030ab57f523f33d5396c6a81396d440756f5f 3.0.0 0df781b261f55bbde73d8c92da3f99397000972f
|
||||||
|
CURRENT_NEXTCLOUD_VER="24.0.12"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
InstallNextcloud $nextcloud_ver $nextcloud_hash $contacts_ver $contacts_hash $calendar_ver $calendar_hash $user_external_ver $user_external_hash
|
InstallNextcloud $nextcloud_ver $nextcloud_hash $contacts_ver $contacts_hash $calendar_ver $calendar_hash $user_external_ver $user_external_hash
|
||||||
@@ -200,13 +222,13 @@ fi
|
|||||||
|
|
||||||
# Setup Nextcloud if the Nextcloud database does not yet exist. Running setup when
|
# Setup Nextcloud if the Nextcloud database does not yet exist. Running setup when
|
||||||
# the database does exist wipes the database and user data.
|
# 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
|
# Create user data directory
|
||||||
mkdir -p $STORAGE_ROOT/owncloud
|
mkdir -p "$STORAGE_ROOT/owncloud"
|
||||||
|
|
||||||
# Create an initial configuration file.
|
# Create an initial configuration file.
|
||||||
instanceid=oc$(echo $PRIMARY_HOSTNAME | sha1sum | fold -w 10 | head -n 1)
|
instanceid=oc$(echo "$PRIMARY_HOSTNAME" | sha1sum | fold -w 10 | head -n 1)
|
||||||
cat > $STORAGE_ROOT/owncloud/config.php <<EOF;
|
cat > "$STORAGE_ROOT/owncloud/config.php" <<EOF;
|
||||||
<?php
|
<?php
|
||||||
\$CONFIG = array (
|
\$CONFIG = array (
|
||||||
'datadirectory' => '$STORAGE_ROOT/owncloud',
|
'datadirectory' => '$STORAGE_ROOT/owncloud',
|
||||||
@@ -259,12 +281,12 @@ EOF
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Set permissions
|
# 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.
|
# 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
|
# It also wipes it if it exists. And it updates config.php with database
|
||||||
# settings and deletes the autoconfig.php file.
|
# 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
|
fi
|
||||||
|
|
||||||
# Update config.php.
|
# Update config.php.
|
||||||
@@ -280,16 +302,16 @@ fi
|
|||||||
# Use PHP to read the settings file, modify it, and write out the new settings array.
|
# Use PHP to read the settings file, modify it, and write out the new settings array.
|
||||||
TIMEZONE=$(cat /etc/timezone)
|
TIMEZONE=$(cat /etc/timezone)
|
||||||
CONFIG_TEMP=$(/bin/mktemp)
|
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
|
<?php
|
||||||
include("$STORAGE_ROOT/owncloud/config.php");
|
include("$STORAGE_ROOT/owncloud/config.php");
|
||||||
|
|
||||||
\$CONFIG['config_is_read_only'] = true;
|
\$CONFIG['config_is_read_only'] = false;
|
||||||
|
|
||||||
\$CONFIG['trusted_domains'] = array('$PRIMARY_HOSTNAME');
|
\$CONFIG['trusted_domains'] = array('$PRIMARY_HOSTNAME');
|
||||||
|
|
||||||
\$CONFIG['memcache.local'] = '\OC\Memcache\APCu';
|
\$CONFIG['memcache.local'] = '\OC\Memcache\APCu';
|
||||||
\$CONFIG['overwrite.cli.url'] = '/cloud';
|
\$CONFIG['overwrite.cli.url'] = 'https://${PRIMARY_HOSTNAME}/cloud';
|
||||||
\$CONFIG['mail_from_address'] = 'administrator'; # just the local part, matches our master administrator address
|
\$CONFIG['mail_from_address'] = 'administrator'; # just the local part, matches our master administrator address
|
||||||
|
|
||||||
\$CONFIG['logtimezone'] = '$TIMEZONE';
|
\$CONFIG['logtimezone'] = '$TIMEZONE';
|
||||||
@@ -311,31 +333,32 @@ var_export(\$CONFIG);
|
|||||||
echo ";";
|
echo ";";
|
||||||
?>
|
?>
|
||||||
EOF
|
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.
|
# Enable/disable apps. Note that this must be done after the Nextcloud setup.
|
||||||
# The firstrunwizard gave Josh all sorts of problems, so disabling that.
|
# The firstrunwizard gave Josh all sorts of problems, so disabling that.
|
||||||
# user_external is what allows Nextcloud to use IMAP for login. The contacts
|
# user_external is what allows Nextcloud to use IMAP for login. The contacts
|
||||||
# and calendar apps are the extensions we really care about here.
|
# 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: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 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 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:enable calendar
|
||||||
|
|
||||||
# When upgrading, run the upgrade script again now that apps are enabled. It seems like
|
# 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?
|
# 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).
|
# Check for success (0=ok, 3=no upgrade needed).
|
||||||
sudo -u www-data php$PHP_VER /usr/local/lib/owncloud/occ upgrade
|
sudo -u www-data php"$PHP_VER" /usr/local/lib/owncloud/occ upgrade
|
||||||
if [ \( $? -ne 0 \) -a \( $? -ne 3 \) ]; then exit 1; fi
|
E=$?
|
||||||
|
if [ $E -ne 0 ] && [ $E -ne 3 ]; then exit 1; fi
|
||||||
|
|
||||||
# Disable default apps that we don't support
|
# Disable default apps that we don't support
|
||||||
sudo -u www-data \
|
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)
|
| (grep -v "No such app enabled" || /bin/true)
|
||||||
|
|
||||||
# Set PHP FPM values to support large file uploads
|
# Set PHP FPM values to support large file uploads
|
||||||
# (semicolon is the comment character in this file, hashes produce deprecation warnings)
|
# (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 \
|
upload_max_filesize=16G \
|
||||||
post_max_size=16G \
|
post_max_size=16G \
|
||||||
output_buffering=16384 \
|
output_buffering=16384 \
|
||||||
@@ -344,7 +367,7 @@ tools/editconf.py /etc/php/$PHP_VER/fpm/php.ini -c ';' \
|
|||||||
short_open_tag=On
|
short_open_tag=On
|
||||||
|
|
||||||
# Set Nextcloud recommended opcache settings
|
# 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=1 \
|
||||||
opcache.enable_cli=1 \
|
opcache.enable_cli=1 \
|
||||||
opcache.interned_strings_buffer=8 \
|
opcache.interned_strings_buffer=8 \
|
||||||
@@ -353,20 +376,45 @@ tools/editconf.py /etc/php/$PHP_VER/cli/conf.d/10-opcache.ini -c ';' \
|
|||||||
opcache.save_comments=1 \
|
opcache.save_comments=1 \
|
||||||
opcache.revalidate_freq=1
|
opcache.revalidate_freq=1
|
||||||
|
|
||||||
# Migrate users_external data from <0.6.0 to version 3.0.0 (see https://github.com/nextcloud/user_external).
|
# Migrate users_external data from <0.6.0 to version 3.0.0
|
||||||
|
# (see https://github.com/nextcloud/user_external).
|
||||||
# This version was probably in use in Mail-in-a-Box v0.41 (February 26, 2019) and earlier.
|
# 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
|
# We moved to v0.6.3 in 193763f8. Ignore errors - maybe there are duplicated users with the
|
||||||
# correct backend already.
|
# 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 cron job for Nextcloud.
|
# Set up a general cron job for Nextcloud.
|
||||||
|
# Also add another job for Calendar updates, per advice in the Nextcloud docs
|
||||||
|
# https://docs.nextcloud.com/server/24/admin_manual/groupware/calendar.html#background-jobs
|
||||||
cat > /etc/cron.d/mailinabox-nextcloud << EOF;
|
cat > /etc/cron.d/mailinabox-nextcloud << EOF;
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Mail-in-a-Box
|
# Mail-in-a-Box
|
||||||
*/5 * * * * root sudo -u www-data php$PHP_VER -f /usr/local/lib/owncloud/cron.php
|
*/5 * * * * root sudo -u www-data php$PHP_VER -f /usr/local/lib/owncloud/cron.php
|
||||||
|
*/5 * * * * root sudo -u www-data php$PHP_VER -f /usr/local/lib/owncloud/occ dav:send-event-reminders
|
||||||
EOF
|
EOF
|
||||||
chmod +x /etc/cron.d/mailinabox-nextcloud
|
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
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
# Rotate the nextcloud.log file
|
||||||
|
cat > /etc/logrotate.d/nextcloud <<EOF
|
||||||
|
# Nextcloud logs
|
||||||
|
$STORAGE_ROOT/owncloud/nextcloud.log {
|
||||||
|
size 10M
|
||||||
|
create 640 www-data www-data
|
||||||
|
rotate 30
|
||||||
|
copytruncate
|
||||||
|
missingok
|
||||||
|
compress
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
# There's nothing much of interest that a user could do as an admin for Nextcloud,
|
# There's nothing much of interest that a user could do as an admin for Nextcloud,
|
||||||
# and there's a lot they could mess up, so we don't make any users admins of Nextcloud.
|
# and there's a lot they could mess up, so we don't make any users admins of Nextcloud.
|
||||||
# But if we wanted to, we would do this:
|
# But if we wanted to, we would do this:
|
||||||
@@ -377,4 +425,4 @@ chmod +x /etc/cron.d/mailinabox-nextcloud
|
|||||||
# ```
|
# ```
|
||||||
|
|
||||||
# Enable PHP modules and restart PHP.
|
# Enable PHP modules and restart PHP.
|
||||||
restart_service php$PHP_VER-fpm
|
restart_service php"$PHP_VER"-fpm
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
# Are we running as root?
|
# Are we running as root?
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
echo "This script must be run as root. Please re-run like this:"
|
echo "This script must be run as root. Please re-run like this:"
|
||||||
@@ -26,16 +27,16 @@ fi
|
|||||||
#
|
#
|
||||||
# Skip the check if we appear to be running inside of Vagrant, because that's really just for testing.
|
# Skip the check if we appear to be running inside of Vagrant, because that's really just for testing.
|
||||||
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}')
|
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}')
|
||||||
if [ $TOTAL_PHYSICAL_MEM -lt 490000 ]; then
|
if [ "$TOTAL_PHYSICAL_MEM" -lt 490000 ]; then
|
||||||
if [ ! -d /vagrant ]; then
|
if [ ! -d /vagrant ]; then
|
||||||
TOTAL_PHYSICAL_MEM=$(expr \( \( $TOTAL_PHYSICAL_MEM \* 1024 \) / 1000 \) / 1000)
|
TOTAL_PHYSICAL_MEM=$(( ( ( TOTAL_PHYSICAL_MEM * 1024 ) / 1000 ) / 1000 ))
|
||||||
echo "Your Mail-in-a-Box needs more memory (RAM) to function properly."
|
echo "Your Mail-in-a-Box needs more memory (RAM) to function properly."
|
||||||
echo "Please provision a machine with at least 512 MB, 1 GB recommended."
|
echo "Please provision a machine with at least 512 MB, 1 GB recommended."
|
||||||
echo "This machine has $TOTAL_PHYSICAL_MEM MB memory."
|
echo "This machine has $TOTAL_PHYSICAL_MEM MB memory."
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [ $TOTAL_PHYSICAL_MEM -lt 750000 ]; then
|
if [ "$TOTAL_PHYSICAL_MEM" -lt 750000 ]; then
|
||||||
echo "WARNING: Your Mail-in-a-Box has less than 768 MB of memory."
|
echo "WARNING: Your Mail-in-a-Box has less than 768 MB of memory."
|
||||||
echo " It might run unreliably when under heavy load."
|
echo " It might run unreliably when under heavy load."
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
if [ -z "${NONINTERACTIVE:-}" ]; then
|
if [ -z "${NONINTERACTIVE:-}" ]; then
|
||||||
# Install 'dialog' so we can ask the user questions. The original motivation for
|
# 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,
|
# 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.
|
# Also install dependencies needed to validate the email address.
|
||||||
if [ ! -f /usr/bin/dialog ] || [ ! -f /usr/bin/python3 ] || [ ! -f /usr/bin/pip3 ]; then
|
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 -q -q update
|
||||||
apt_get_quiet install dialog python3 python3-pip || exit 1
|
apt_get_quiet install dialog python3 python3-pip || exit 1
|
||||||
fi
|
fi
|
||||||
@@ -31,7 +32,7 @@ if [ -z "${PRIMARY_HOSTNAME:-}" ]; then
|
|||||||
# domain the user possibly wants to use is example.com then.
|
# domain the user possibly wants to use is example.com then.
|
||||||
# We strip the string "box." from the hostname to get the mail
|
# We strip the string "box." from the hostname to get the mail
|
||||||
# domain. If the hostname differs, nothing happens here.
|
# 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
|
# This is the first run. Ask the user for his email address so we can
|
||||||
# provide the best default for the box's hostname.
|
# provide the best default for the box's hostname.
|
||||||
@@ -55,7 +56,7 @@ you really want.
|
|||||||
do
|
do
|
||||||
input_box "Your Email Address" \
|
input_box "Your Email Address" \
|
||||||
"That's not a valid email address.\n\nWhat email address are you setting this box up to manage?" \
|
"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
|
EMAIL_ADDR
|
||||||
if [ -z "$EMAIL_ADDR" ]; then
|
if [ -z "$EMAIL_ADDR" ]; then
|
||||||
# user hit ESC/cancel
|
# 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
|
# 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.
|
# '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
|
fi
|
||||||
|
|
||||||
input_box "Hostname" \
|
input_box "Hostname" \
|
||||||
@@ -74,7 +75,7 @@ you really want.
|
|||||||
address, so we're suggesting $DEFAULT_PRIMARY_HOSTNAME.
|
address, so we're suggesting $DEFAULT_PRIMARY_HOSTNAME.
|
||||||
\n\nYou can change it, but we recommend you don't.
|
\n\nYou can change it, but we recommend you don't.
|
||||||
\n\nHostname:" \
|
\n\nHostname:" \
|
||||||
$DEFAULT_PRIMARY_HOSTNAME \
|
"$DEFAULT_PRIMARY_HOSTNAME" \
|
||||||
PRIMARY_HOSTNAME
|
PRIMARY_HOSTNAME
|
||||||
|
|
||||||
if [ -z "$PRIMARY_HOSTNAME" ]; then
|
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
|
# On the first run, if we got an answer from the Internet then don't
|
||||||
# ask the user.
|
# 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
|
PUBLIC_IP=$GUESSED_IP
|
||||||
|
|
||||||
# Otherwise on the first run at least provide a default.
|
# Otherwise on the first run at least provide a default.
|
||||||
@@ -109,7 +110,7 @@ if [ -z "${PUBLIC_IP:-}" ]; then
|
|||||||
input_box "Public IP Address" \
|
input_box "Public IP Address" \
|
||||||
"Enter the public IP address of this machine, as given to you by your ISP.
|
"Enter the public IP address of this machine, as given to you by your ISP.
|
||||||
\n\nPublic IP address:" \
|
\n\nPublic IP address:" \
|
||||||
${DEFAULT_PUBLIC_IP:-} \
|
"${DEFAULT_PUBLIC_IP:-}" \
|
||||||
PUBLIC_IP
|
PUBLIC_IP
|
||||||
|
|
||||||
if [ -z "$PUBLIC_IP" ]; then
|
if [ -z "$PUBLIC_IP" ]; then
|
||||||
@@ -125,7 +126,7 @@ if [ -z "${PUBLIC_IPV6:-}" ]; then
|
|||||||
# Ask the Internet.
|
# Ask the Internet.
|
||||||
GUESSED_IP=$(get_publicip_from_web_service 6)
|
GUESSED_IP=$(get_publicip_from_web_service 6)
|
||||||
MATCHED=0
|
MATCHED=0
|
||||||
if [[ -z "${DEFAULT_PUBLIC_IPV6:-}" && ! -z "$GUESSED_IP" ]]; then
|
if [[ -z "${DEFAULT_PUBLIC_IPV6:-}" && -n "$GUESSED_IP" ]]; then
|
||||||
PUBLIC_IPV6=$GUESSED_IP
|
PUBLIC_IPV6=$GUESSED_IP
|
||||||
elif [[ "${DEFAULT_PUBLIC_IPV6:-}" == "$GUESSED_IP" ]]; then
|
elif [[ "${DEFAULT_PUBLIC_IPV6:-}" == "$GUESSED_IP" ]]; then
|
||||||
# No IPv6 entered and machine seems to have none, or what
|
# 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.
|
"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\nLeave blank if the machine does not have an IPv6 address.
|
||||||
\n\nPublic IPv6 address:" \
|
\n\nPublic IPv6 address:" \
|
||||||
${DEFAULT_PUBLIC_IPV6:-} \
|
"${DEFAULT_PUBLIC_IPV6:-}" \
|
||||||
PUBLIC_IPV6
|
PUBLIC_IPV6
|
||||||
|
|
||||||
if [ ! $PUBLIC_IPV6_EXITCODE ]; then
|
if [ ! -n "$PUBLIC_IPV6_EXITCODE" ]; then
|
||||||
# user hit ESC/cancel
|
# user hit ESC/cancel
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
@@ -197,7 +198,7 @@ fi
|
|||||||
echo
|
echo
|
||||||
echo "Primary Hostname: $PRIMARY_HOSTNAME"
|
echo "Primary Hostname: $PRIMARY_HOSTNAME"
|
||||||
echo "Public IP Address: $PUBLIC_IP"
|
echo "Public IP Address: $PUBLIC_IP"
|
||||||
if [ ! -z "$PUBLIC_IPV6" ]; then
|
if [ -n "$PUBLIC_IPV6" ]; then
|
||||||
echo "Public IPv6 Address: $PUBLIC_IPV6"
|
echo "Public IPv6 Address: $PUBLIC_IPV6"
|
||||||
fi
|
fi
|
||||||
if [ "$PRIVATE_IP" != "$PUBLIC_IP" ]; then
|
if [ "$PRIVATE_IP" != "$PUBLIC_IP" ]; then
|
||||||
@@ -207,6 +208,6 @@ if [ "$PRIVATE_IPV6" != "$PUBLIC_IPV6" ]; then
|
|||||||
echo "Private IPv6 Address: $PRIVATE_IPV6"
|
echo "Private IPv6 Address: $PRIVATE_IPV6"
|
||||||
fi
|
fi
|
||||||
if [ -f /usr/bin/git ] && [ -d .git ]; then
|
if [ -f /usr/bin/git ] && [ -d .git ]; then
|
||||||
echo "Mail-in-a-Box Version: " $(git describe)
|
echo "Mail-in-a-Box Version: $(git describe --always)"
|
||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -135,11 +135,11 @@ EOF
|
|||||||
# the filemode in the config file.
|
# the filemode in the config file.
|
||||||
|
|
||||||
tools/editconf.py /etc/spamassassin/local.cf -s \
|
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
|
bayes_file_mode=0666
|
||||||
|
|
||||||
mkdir -p $STORAGE_ROOT/mail/spamassassin
|
mkdir -p "$STORAGE_ROOT/mail/spamassassin"
|
||||||
chown -R spampd:spampd $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
|
# 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
|
# 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,
|
# 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.
|
# ensure they are group-writable so that the Dovecot process has access.
|
||||||
sudo -u spampd /usr/bin/sa-learn --sync 2>/dev/null
|
sudo -u spampd /usr/bin/sa-learn --sync 2>/dev/null
|
||||||
chmod -R 660 $STORAGE_ROOT/mail/spamassassin
|
chmod -R 660 "$STORAGE_ROOT/mail/spamassassin"
|
||||||
chmod 770 $STORAGE_ROOT/mail/spamassassin
|
chmod 770 "$STORAGE_ROOT/mail/spamassassin"
|
||||||
|
|
||||||
# Initial training?
|
# Initial training?
|
||||||
# sa-learn --ham storage/mail/mailboxes/*/*/cur/
|
# 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.
|
# Show a status line if we are going to take any action in this file.
|
||||||
if [ ! -f /usr/bin/openssl ] \
|
if [ ! -f /usr/bin/openssl ] \
|
||||||
|| [ ! -f $STORAGE_ROOT/ssl/ssl_private_key.pem ] \
|
|| [ ! -f "$STORAGE_ROOT/ssl/ssl_private_key.pem" ] \
|
||||||
|| [ ! -f $STORAGE_ROOT/ssl/ssl_certificate.pem ] \
|
|| [ ! -f "$STORAGE_ROOT/ssl/ssl_certificate.pem" ] \
|
||||||
|| [ ! -f $STORAGE_ROOT/ssl/dh2048.pem ]; then
|
|| [ ! -f "$STORAGE_ROOT/ssl/dh2048.pem" ]; then
|
||||||
echo "Creating initial SSL certificate and perfect forward secrecy Diffie-Hellman parameters..."
|
echo "Creating initial SSL certificate and perfect forward secrecy Diffie-Hellman parameters..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ apt_install openssl
|
|||||||
|
|
||||||
# Create a directory to store TLS-related things like "SSL" certificates.
|
# 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.
|
# 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
|
# 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.
|
# 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.
|
# Set the umask so the key file is never world-readable.
|
||||||
(umask 077; hide_output \
|
(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
|
fi
|
||||||
|
|
||||||
# Generate a self-signed SSL certificate because things like nginx, dovecot,
|
# 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
|
# 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.
|
# 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.
|
# Generate a certificate signing request.
|
||||||
CSR=/tmp/ssl_cert_sign_req-$$.csr
|
CSR=/tmp/ssl_cert_sign_req-$$.csr
|
||||||
hide_output \
|
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"
|
-sha256 -subj "/CN=$PRIMARY_HOSTNAME"
|
||||||
|
|
||||||
# Generate the self-signed certificate.
|
# Generate the self-signed certificate.
|
||||||
CERT=$STORAGE_ROOT/ssl/$PRIMARY_HOSTNAME-selfsigned-$(date --rfc-3339=date | sed s/-//g).pem
|
CERT=$STORAGE_ROOT/ssl/$PRIMARY_HOSTNAME-selfsigned-$(date --rfc-3339=date | sed s/-//g).pem
|
||||||
hide_output \
|
hide_output \
|
||||||
openssl x509 -req -days 365 \
|
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.
|
# Delete the certificate signing request because it has no other purpose.
|
||||||
rm -f $CSR
|
rm -f $CSR
|
||||||
|
|
||||||
# Symlink the certificate into the system certificate path, so system services
|
# Symlink the certificate into the system certificate path, so system services
|
||||||
# can find it.
|
# can find it.
|
||||||
ln -s $CERT $STORAGE_ROOT/ssl/ssl_certificate.pem
|
ln -s "$CERT" "$STORAGE_ROOT/ssl/ssl_certificate.pem"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Generate some Diffie-Hellman cipher bits.
|
# Generate some Diffie-Hellman cipher bits.
|
||||||
# openssl's default bit length for this is 1024 bits, but we'll create
|
# openssl's default bit length for this is 1024 bits, but we'll create
|
||||||
# 2048 bits of bits per the latest recommendations.
|
# 2048 bits of bits per the latest recommendations.
|
||||||
if [ ! -f $STORAGE_ROOT/ssl/dh2048.pem ]; then
|
if [ ! -f "$STORAGE_ROOT/ssl/dh2048.pem" ]; then
|
||||||
openssl dhparam -out $STORAGE_ROOT/ssl/dh2048.pem 2048
|
openssl dhparam -out "$STORAGE_ROOT/ssl/dh2048.pem" 2048
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ if [ -f /etc/mailinabox.conf ]; then
|
|||||||
|
|
||||||
# Load the old .conf file to get existing configuration options loaded
|
# Load the old .conf file to get existing configuration options loaded
|
||||||
# into variables with a DEFAULT_ prefix.
|
# into variables with a DEFAULT_ prefix.
|
||||||
cat /etc/mailinabox.conf | sed s/^/DEFAULT_/ > /tmp/mailinabox.prev.conf
|
sed s/^/DEFAULT_/ /etc/mailinabox.conf > /tmp/mailinabox.prev.conf
|
||||||
source /tmp/mailinabox.prev.conf
|
source /tmp/mailinabox.prev.conf
|
||||||
rm -f /tmp/mailinabox.prev.conf
|
rm -f /tmp/mailinabox.prev.conf
|
||||||
else
|
else
|
||||||
@@ -46,7 +46,7 @@ fi
|
|||||||
# in the first dialog prompt, so we should do this before that starts.
|
# in the first dialog prompt, so we should do this before that starts.
|
||||||
cat > /usr/local/bin/mailinabox << EOF;
|
cat > /usr/local/bin/mailinabox << EOF;
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
cd $(pwd)
|
cd $PWD
|
||||||
source setup/start.sh
|
source setup/start.sh
|
||||||
EOF
|
EOF
|
||||||
chmod +x /usr/local/bin/mailinabox
|
chmod +x /usr/local/bin/mailinabox
|
||||||
@@ -75,17 +75,17 @@ fi
|
|||||||
# migration (schema) number for the files stored there, assume this is a fresh
|
# 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
|
# installation to that directory and write the file to contain the current
|
||||||
# migration number for this version of Mail-in-a-Box.
|
# migration number for this version of Mail-in-a-Box.
|
||||||
if ! id -u $STORAGE_USER >/dev/null 2>&1; then
|
if ! id -u "$STORAGE_USER" >/dev/null 2>&1; then
|
||||||
useradd -m $STORAGE_USER
|
useradd -m "$STORAGE_USER"
|
||||||
fi
|
fi
|
||||||
if [ ! -d $STORAGE_ROOT ]; then
|
if [ ! -d "$STORAGE_ROOT" ]; then
|
||||||
mkdir -p $STORAGE_ROOT
|
mkdir -p "$STORAGE_ROOT"
|
||||||
fi
|
fi
|
||||||
f=$STORAGE_ROOT
|
f=$STORAGE_ROOT
|
||||||
while [[ $f != / ]]; do chmod a+rx "$f"; f=$(dirname "$f"); done;
|
while [[ $f != / ]]; do chmod a+rx "$f"; f=$(dirname "$f"); done;
|
||||||
if [ ! -f $STORAGE_ROOT/mailinabox.version ]; then
|
if [ ! -f "$STORAGE_ROOT/mailinabox.version" ]; then
|
||||||
setup/migrate.py --current > $STORAGE_ROOT/mailinabox.version
|
setup/migrate.py --current > "$STORAGE_ROOT/mailinabox.version"
|
||||||
chown $STORAGE_USER.$STORAGE_USER $STORAGE_ROOT/mailinabox.version
|
chown "$STORAGE_USER:$STORAGE_USER" "$STORAGE_ROOT/mailinabox.version"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Save the global options in /etc/mailinabox.conf so that standalone
|
# 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...
|
# Wait for the management daemon to start...
|
||||||
until nc -z -w 4 127.0.0.1 10222
|
until nc -z -w 4 127.0.0.1 10222
|
||||||
do
|
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
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -142,41 +142,41 @@ source setup/firstuser.sh
|
|||||||
# We'd let certbot ask the user interactively, but when this script is
|
# 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
|
# run in the recommended curl-pipe-to-bash method there is no TTY and
|
||||||
# certbot will fail if it tries to ask.
|
# 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 "-----------------------------------------------"
|
echo "-----------------------------------------------"
|
||||||
echo "Mail-in-a-Box uses Let's Encrypt to provision free SSL/TLS certificates"
|
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 "to enable HTTPS connections to your box. We're automatically"
|
||||||
echo "agreeing you to their subscriber agreement. See https://letsencrypt.org."
|
echo "agreeing you to their subscriber agreement. See https://letsencrypt.org."
|
||||||
echo
|
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
|
fi
|
||||||
|
|
||||||
# Done.
|
# Done.
|
||||||
echo
|
echo
|
||||||
echo "-----------------------------------------------"
|
echo "-----------------------------------------------"
|
||||||
echo
|
echo
|
||||||
echo Your Mail-in-a-Box is running.
|
echo "Your Mail-in-a-Box is running."
|
||||||
echo
|
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
|
echo
|
||||||
if management/status_checks.py --check-primary-hostname; then
|
if management/status_checks.py --check-primary-hostname; then
|
||||||
# Show the nice URL if it appears to be resolving and has a valid certificate.
|
# 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
|
||||||
echo "If you have a DNS problem put the box's IP address in the URL"
|
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:"
|
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=//"
|
| sed "s/SHA256 Fingerprint=//i"
|
||||||
else
|
else
|
||||||
echo https://$PUBLIC_IP/admin
|
echo "https://$PUBLIC_IP/admin"
|
||||||
echo
|
echo
|
||||||
echo You will be alerted that the website has an invalid certificate. Check that
|
echo "You will be alerted that the website has an invalid certificate. Check that"
|
||||||
echo the certificate fingerprint matches:
|
echo "the certificate fingerprint matches:"
|
||||||
echo
|
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=//"
|
| sed "s/SHA256 Fingerprint=//i"
|
||||||
echo
|
echo
|
||||||
echo Then you can confirm the security exception and continue.
|
echo "Then you can confirm the security exception and continue."
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
source /etc/mailinabox.conf
|
source /etc/mailinabox.conf
|
||||||
source setup/functions.sh # load our functions
|
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
|
# First set the hostname in the configuration file, then activate the setting
|
||||||
|
|
||||||
echo $PRIMARY_HOSTNAME > /etc/hostname
|
echo "$PRIMARY_HOSTNAME" > /etc/hostname
|
||||||
hostname $PRIMARY_HOSTNAME
|
hostname "$PRIMARY_HOSTNAME"
|
||||||
|
|
||||||
# ### Fix permissions
|
# ### Fix permissions
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ chmod g-w /etc /etc/default /usr
|
|||||||
# See https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-ubuntu-14-04
|
# See https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-ubuntu-14-04
|
||||||
# for reference
|
# for reference
|
||||||
|
|
||||||
SWAP_MOUNTED=$(cat /proc/swaps | tail -n+2)
|
SWAP_MOUNTED=$(tail -n+2 /proc/swaps)
|
||||||
SWAP_IN_FSTAB=$(grep "swap" /etc/fstab || /bin/true)
|
SWAP_IN_FSTAB=$(grep "swap" /etc/fstab || /bin/true)
|
||||||
ROOT_IS_BTRFS=$(grep "\/ .*btrfs" /proc/mounts || /bin/true)
|
ROOT_IS_BTRFS=$(grep "\/ .*btrfs" /proc/mounts || /bin/true)
|
||||||
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}' || /bin/true)
|
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}' || /bin/true)
|
||||||
@@ -53,14 +54,14 @@ if
|
|||||||
[ -z "$SWAP_IN_FSTAB" ] &&
|
[ -z "$SWAP_IN_FSTAB" ] &&
|
||||||
[ ! -e /swapfile ] &&
|
[ ! -e /swapfile ] &&
|
||||||
[ -z "$ROOT_IS_BTRFS" ] &&
|
[ -z "$ROOT_IS_BTRFS" ] &&
|
||||||
[ $TOTAL_PHYSICAL_MEM -lt 1900000 ] &&
|
[ "$TOTAL_PHYSICAL_MEM" -lt 1900000 ] &&
|
||||||
[ $AVAILABLE_DISK_SPACE -gt 5242880 ]
|
[ "$AVAILABLE_DISK_SPACE" -gt 5242880 ]
|
||||||
then
|
then
|
||||||
echo "Adding a swap file to the system..."
|
echo "Adding a swap file to the system..."
|
||||||
|
|
||||||
# Allocate and activate the swap file. Allocate in 1KB chuncks
|
# Allocate and activate the swap file. Allocate in 1KB chuncks
|
||||||
# doing it in one go, could fail on low memory systems
|
# 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
|
if [ -e /swapfile ]; then
|
||||||
chmod 600 /swapfile
|
chmod 600 /swapfile
|
||||||
hide_output mkswap /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
|
# of things from Ubuntu, as well as the directory of packages provide by the
|
||||||
# PPAs so we can install those packages later.
|
# PPAs so we can install those packages later.
|
||||||
|
|
||||||
echo Updating system packages...
|
echo "Updating system packages..."
|
||||||
hide_output apt-get update
|
hide_output apt-get update
|
||||||
apt_get_quiet upgrade
|
apt_get_quiet upgrade
|
||||||
|
|
||||||
@@ -135,7 +136,7 @@ apt_get_quiet autoremove
|
|||||||
# * bc: allows us to do math to compute sane defaults
|
# * bc: allows us to do math to compute sane defaults
|
||||||
# * openssh-client: provides ssh-keygen
|
# * openssh-client: provides ssh-keygen
|
||||||
|
|
||||||
echo Installing system packages...
|
echo "Installing system packages..."
|
||||||
apt_install python3 python3-dev python3-pip python3-setuptools \
|
apt_install python3 python3-dev python3-pip python3-setuptools \
|
||||||
netcat-openbsd wget curl git sudo coreutils bc file \
|
netcat-openbsd wget curl git sudo coreutils bc file \
|
||||||
pollinate openssh-client unzip \
|
pollinate openssh-client unzip \
|
||||||
@@ -164,7 +165,7 @@ fi
|
|||||||
# not likely the user will want to change this, so we only ask on first
|
# not likely the user will want to change this, so we only ask on first
|
||||||
# setup.
|
# setup.
|
||||||
if [ -z "${NONINTERACTIVE:-}" ]; then
|
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
|
# If the file is missing or this is the user's first time running
|
||||||
# Mail-in-a-Box setup, run the interactive timezone configuration
|
# Mail-in-a-Box setup, run the interactive timezone configuration
|
||||||
# tool.
|
# tool.
|
||||||
@@ -226,7 +227,7 @@ fi
|
|||||||
# hardware entropy to get going, by drawing from /dev/random. haveged makes this
|
# hardware entropy to get going, by drawing from /dev/random. haveged makes this
|
||||||
# less likely to stall for very long.
|
# 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
|
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
|
# 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
|
# settings, find the port it is supposedly running on, and open that port #NODOC
|
||||||
# too. #NODOC
|
# too. #NODOC
|
||||||
SSH_PORT=$(sshd -T 2>/dev/null | grep "^port " | sed "s/port //") #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
|
if [ "$SSH_PORT" != "22" ]; then
|
||||||
|
|
||||||
echo Opening alternate SSH port $SSH_PORT. #NODOC
|
echo "Opening alternate SSH port $SSH_PORT." #NODOC
|
||||||
ufw_limit $SSH_PORT #NODOC
|
ufw_limit "$SSH_PORT" #NODOC
|
||||||
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -360,8 +361,7 @@ systemctl restart systemd-resolved
|
|||||||
# Configure the Fail2Ban installation to prevent dumb bruce-force attacks against dovecot, postfix, ssh, etc.
|
# Configure the Fail2Ban installation to prevent dumb bruce-force attacks against dovecot, postfix, ssh, etc.
|
||||||
rm -f /etc/fail2ban/jail.local # we used to use this file but don't anymore
|
rm -f /etc/fail2ban/jail.local # we used to use this file but don't anymore
|
||||||
rm -f /etc/fail2ban/jail.d/defaults-debian.conf # removes default config so we can manage all of fail2ban rules in one config
|
rm -f /etc/fail2ban/jail.d/defaults-debian.conf # removes default config so we can manage all of fail2ban rules in one config
|
||||||
cat conf/fail2ban/jails.conf \
|
sed "s/PUBLIC_IPV6/$PUBLIC_IPV6/g" conf/fail2ban/jails.conf \
|
||||||
| sed "s/PUBLIC_IPV6/$PUBLIC_IPV6/g" \
|
|
||||||
| sed "s/PUBLIC_IP/$PUBLIC_IP/g" \
|
| sed "s/PUBLIC_IP/$PUBLIC_IP/g" \
|
||||||
| sed "s#STORAGE_ROOT#$STORAGE_ROOT#" \
|
| sed "s#STORAGE_ROOT#$STORAGE_ROOT#" \
|
||||||
> /etc/fail2ban/jail.d/mailinabox.conf
|
> /etc/fail2ban/jail.d/mailinabox.conf
|
||||||
@@ -373,3 +373,5 @@ cp -f conf/fail2ban/filter.d/* /etc/fail2ban/filter.d/
|
|||||||
# scripts will ensure the files exist and then fail2ban is given another
|
# scripts will ensure the files exist and then fail2ban is given another
|
||||||
# restart at the very end of setup.
|
# restart at the very end of setup.
|
||||||
restart_service fail2ban
|
restart_service fail2ban
|
||||||
|
|
||||||
|
systemctl enable fail2ban
|
||||||
|
|||||||
47
setup/web.sh
47
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
|
# Some Ubuntu images start off with Apache. Remove it since we
|
||||||
# will use nginx. Use autoremove to remove any Apache depenencies.
|
# will use nginx. Use autoremove to remove any Apache depenencies.
|
||||||
if [ -f /usr/sbin/apache2 ]; then
|
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 apache2 apache2-*
|
||||||
hide_output apt-get -y --purge autoremove
|
hide_output apt-get -y --purge autoremove
|
||||||
fi
|
fi
|
||||||
@@ -19,7 +19,7 @@ fi
|
|||||||
|
|
||||||
echo "Installing Nginx (web server)..."
|
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
|
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;"
|
ssl_protocols="TLSv1.2 TLSv1.3;"
|
||||||
|
|
||||||
# Tell PHP not to expose its version number in the X-Powered-By header.
|
# 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
|
expose_php=Off
|
||||||
|
|
||||||
# Set PHPs default charset to UTF-8, since we use it. See #367.
|
# 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"
|
default_charset="UTF-8"
|
||||||
|
|
||||||
# Configure the path environment for php-fpm
|
# 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 \
|
env[PATH]=/usr/local/bin:/usr/bin:/bin \
|
||||||
|
|
||||||
# Configure php-fpm based on the amount of memory the machine has
|
# 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.
|
# 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
|
# 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)
|
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
|
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=ondemand \
|
||||||
pm.max_children=8 \
|
pm.max_children=8 \
|
||||||
pm.start_servers=2 \
|
pm.start_servers=2 \
|
||||||
pm.min_spare_servers=1 \
|
pm.min_spare_servers=1 \
|
||||||
pm.max_spare_servers=3
|
pm.max_spare_servers=3
|
||||||
elif [ $TOTAL_PHYSICAL_MEM -lt 2000000 ]
|
elif [ "$TOTAL_PHYSICAL_MEM" -lt 2000000 ]
|
||||||
then
|
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=ondemand \
|
||||||
pm.max_children=16 \
|
pm.max_children=16 \
|
||||||
pm.start_servers=4 \
|
pm.start_servers=4 \
|
||||||
pm.min_spare_servers=1 \
|
pm.min_spare_servers=1 \
|
||||||
pm.max_spare_servers=6
|
pm.max_spare_servers=6
|
||||||
elif [ $TOTAL_PHYSICAL_MEM -lt 3000000 ]
|
elif [ "$TOTAL_PHYSICAL_MEM" -lt 3000000 ]
|
||||||
then
|
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=dynamic \
|
||||||
pm.max_children=60 \
|
pm.max_children=60 \
|
||||||
pm.start_servers=6 \
|
pm.start_servers=6 \
|
||||||
pm.min_spare_servers=3 \
|
pm.min_spare_servers=3 \
|
||||||
pm.max_spare_servers=9
|
pm.max_spare_servers=9
|
||||||
else
|
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=dynamic \
|
||||||
pm.max_children=120 \
|
pm.max_children=120 \
|
||||||
pm.start_servers=12 \
|
pm.start_servers=12 \
|
||||||
@@ -103,8 +103,7 @@ fi
|
|||||||
# nginx configuration at /mailinabox-mobileconfig.
|
# nginx configuration at /mailinabox-mobileconfig.
|
||||||
mkdir -p /var/lib/mailinabox
|
mkdir -p /var/lib/mailinabox
|
||||||
chmod a+rx /var/lib/mailinabox
|
chmod a+rx /var/lib/mailinabox
|
||||||
cat conf/ios-profile.xml \
|
sed "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" conf/ios-profile.xml \
|
||||||
| sed "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" \
|
|
||||||
| sed "s/UUID1/$(cat /proc/sys/kernel/random/uuid)/" \
|
| sed "s/UUID1/$(cat /proc/sys/kernel/random/uuid)/" \
|
||||||
| sed "s/UUID2/$(cat /proc/sys/kernel/random/uuid)/" \
|
| sed "s/UUID2/$(cat /proc/sys/kernel/random/uuid)/" \
|
||||||
| sed "s/UUID3/$(cat /proc/sys/kernel/random/uuid)/" \
|
| sed "s/UUID3/$(cat /proc/sys/kernel/random/uuid)/" \
|
||||||
@@ -117,37 +116,35 @@ chmod a+r /var/lib/mailinabox/mobileconfig.xml
|
|||||||
# The format of the file is documented at:
|
# The format of the file is documented at:
|
||||||
# https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
|
# https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
|
||||||
# and https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration/FileFormat/HowTo.
|
# and https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration/FileFormat/HowTo.
|
||||||
cat conf/mozilla-autoconfig.xml \
|
sed "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" conf/mozilla-autoconfig.xml \
|
||||||
| sed "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" \
|
|
||||||
> /var/lib/mailinabox/mozilla-autoconfig.xml
|
> /var/lib/mailinabox/mozilla-autoconfig.xml
|
||||||
chmod a+r /var/lib/mailinabox/mozilla-autoconfig.xml
|
chmod a+r /var/lib/mailinabox/mozilla-autoconfig.xml
|
||||||
|
|
||||||
# Create a generic mta-sts.txt file which is exposed via the
|
# Create a generic mta-sts.txt file which is exposed via the
|
||||||
# nginx configuration at /.well-known/mta-sts.txt
|
# 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/
|
# https://www.uriports.com/blog/mta-sts-explained/
|
||||||
# default mode is "enforce". In /etc/mailinabox.conf change
|
# default mode is "enforce". In /etc/mailinabox.conf change
|
||||||
# "MTA_STS_MODE=testing" which means "Messages will be delivered
|
# "MTA_STS_MODE=testing" which means "Messages will be delivered
|
||||||
# as though there was no failure but a report will be sent if
|
# as though there was no failure but a report will be sent if
|
||||||
# TLS-RPT is configured" if you are not sure you want this yet. Or "none".
|
# TLS-RPT is configured" if you are not sure you want this yet. Or "none".
|
||||||
PUNY_PRIMARY_HOSTNAME=$(echo "$PRIMARY_HOSTNAME" | idn2)
|
PUNY_PRIMARY_HOSTNAME=$(echo "$PRIMARY_HOSTNAME" | idn2)
|
||||||
cat conf/mta-sts.txt \
|
sed "s/MODE/${MTA_STS_MODE}/" conf/mta-sts.txt \
|
||||||
| sed "s/MODE/${MTA_STS_MODE}/" \
|
|
||||||
| sed "s/PRIMARY_HOSTNAME/$PUNY_PRIMARY_HOSTNAME/" \
|
| sed "s/PRIMARY_HOSTNAME/$PUNY_PRIMARY_HOSTNAME/" \
|
||||||
> /var/lib/mailinabox/mta-sts.txt
|
> /var/lib/mailinabox/mta-sts.txt
|
||||||
chmod a+r /var/lib/mailinabox/mta-sts.txt
|
chmod a+r /var/lib/mailinabox/mta-sts.txt
|
||||||
|
|
||||||
# make a default homepage
|
# make a default homepage
|
||||||
if [ -d $STORAGE_ROOT/www/static ]; then mv $STORAGE_ROOT/www/static $STORAGE_ROOT/www/default; fi # migration #NODOC
|
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
|
mkdir -p "$STORAGE_ROOT/www/default"
|
||||||
if [ ! -f $STORAGE_ROOT/www/default/index.html ]; then
|
if [ ! -f "$STORAGE_ROOT/www/default/index.html" ]; then
|
||||||
cp conf/www_default.html $STORAGE_ROOT/www/default/index.html
|
cp conf/www_default.html "$STORAGE_ROOT/www/default/index.html"
|
||||||
fi
|
fi
|
||||||
chown -R $STORAGE_USER $STORAGE_ROOT/www
|
chown -R "$STORAGE_USER" "$STORAGE_ROOT/www"
|
||||||
|
|
||||||
# Start services.
|
# Start services.
|
||||||
restart_service nginx
|
restart_service nginx
|
||||||
restart_service php$PHP_VER-fpm
|
restart_service php"$PHP_VER"-fpm
|
||||||
|
|
||||||
# Open ports.
|
# Open ports.
|
||||||
ufw_allow http
|
ufw_allow http
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ source /etc/mailinabox.conf # load global vars
|
|||||||
echo "Installing Roundcube (webmail)..."
|
echo "Installing Roundcube (webmail)..."
|
||||||
apt_install \
|
apt_install \
|
||||||
dbconfig-common \
|
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}"-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}"-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.
|
# Install Roundcube from source if it is not already present or if it is out of date.
|
||||||
# Combine the Roundcube version number with the commit hash of plugins to track
|
# Combine the Roundcube version number with the commit hash of plugins to track
|
||||||
@@ -35,9 +36,9 @@ apt_install \
|
|||||||
# https://github.com/mstilkerich/rcmcarddav/releases
|
# https://github.com/mstilkerich/rcmcarddav/releases
|
||||||
# The easiest way to get the package hashes is to run this script and get the hash from
|
# The easiest way to get the package hashes is to run this script and get the hash from
|
||||||
# the error message.
|
# the error message.
|
||||||
VERSION=1.6.0
|
VERSION=1.6.5
|
||||||
HASH=fd84b4fac74419bb73e7a3bcae1978d5589c52de
|
HASH=326fcc206cddc00355e98d1e40fd0bcd9baec69f
|
||||||
PERSISTENT_LOGIN_VERSION=bde7b6840c7d91de627ea14e81cf4133cbb3c07a # version 5.2
|
PERSISTENT_LOGIN_VERSION=bde7b6840c7d91de627ea14e81cf4133cbb3c07a # version 5.3
|
||||||
HTML5_NOTIFIER_VERSION=68d9ca194212e15b3c7225eb6085dbcf02fd13d7 # version 0.6.4+
|
HTML5_NOTIFIER_VERSION=68d9ca194212e15b3c7225eb6085dbcf02fd13d7 # version 0.6.4+
|
||||||
CARDDAV_VERSION=4.4.3
|
CARDDAV_VERSION=4.4.3
|
||||||
CARDDAV_HASH=74f8ba7aee33e78beb9de07f7f44b81f6071b644
|
CARDDAV_HASH=74f8ba7aee33e78beb9de07f7f44b81f6071b644
|
||||||
@@ -60,7 +61,7 @@ fi
|
|||||||
if [ $needs_update == 1 ]; then
|
if [ $needs_update == 1 ]; then
|
||||||
# if upgrading from 1.3.x, clear the temp_dir
|
# if upgrading from 1.3.x, clear the temp_dir
|
||||||
if [ -f /usr/local/lib/roundcubemail/version ]; then
|
if [ -f /usr/local/lib/roundcubemail/version ]; then
|
||||||
if [ "$(cat /usr/local/lib/roundcubemail/version | cut -c1-3)" == '1.3' ]; then
|
if [ "$(cut -c1-3 /usr/local/lib/roundcubemail/version)" == '1.3' ]; then
|
||||||
find /var/tmp/roundcubemail/ -type f ! -name 'RCMTEMP*' -delete
|
find /var/tmp/roundcubemail/ -type f ! -name 'RCMTEMP*' -delete
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -169,8 +170,8 @@ cat > ${RCM_PLUGIN_DIR}/carddav/config.inc.php <<EOF;
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Create writable directories.
|
# Create writable directories.
|
||||||
mkdir -p /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
|
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.
|
# Ensure the log file monitored by fail2ban exists, or else fail2ban can't start.
|
||||||
sudo -u www-data touch /var/log/roundcubemail/errors.log
|
sudo -u www-data touch /var/log/roundcubemail/errors.log
|
||||||
@@ -184,31 +185,40 @@ cp ${RCM_PLUGIN_DIR}/password/config.inc.php.dist \
|
|||||||
tools/editconf.py ${RCM_PLUGIN_DIR}/password/config.inc.php \
|
tools/editconf.py ${RCM_PLUGIN_DIR}/password/config.inc.php \
|
||||||
"\$config['password_minimum_length']=8;" \
|
"\$config['password_minimum_length']=8;" \
|
||||||
"\$config['password_db_dsn']='sqlite:///$STORAGE_ROOT/mail/users.sqlite';" \
|
"\$config['password_db_dsn']='sqlite:///$STORAGE_ROOT/mail/users.sqlite';" \
|
||||||
"\$config['password_query']='UPDATE users SET password=%D WHERE email=%u';" \
|
"\$config['password_query']='UPDATE users SET password=%P WHERE email=%u';" \
|
||||||
"\$config['password_dovecotpw']='/usr/bin/doveadm pw';" \
|
"\$config['password_algorithm']='sha512-crypt';" \
|
||||||
"\$config['password_dovecotpw_method']='SHA512-CRYPT';" \
|
"\$config['password_algorithm_prefix']='{SHA512-CRYPT}';"
|
||||||
"\$config['password_dovecotpw_with_method']=true;"
|
|
||||||
|
|
||||||
# so PHP can use doveadm, for the password changing plugin
|
# so PHP can use doveadm, for the password changing plugin
|
||||||
usermod -a -G dovecot www-data
|
usermod -a -G dovecot www-data
|
||||||
|
|
||||||
# set permissions so that PHP can use users.sqlite
|
# set permissions so that PHP can use users.sqlite
|
||||||
# could use dovecot instead of www-data, but not sure it matters
|
# could use dovecot instead of www-data, but not sure it matters
|
||||||
chown root.www-data $STORAGE_ROOT/mail
|
chown root:www-data "$STORAGE_ROOT/mail"
|
||||||
chmod 775 $STORAGE_ROOT/mail
|
chmod 775 "$STORAGE_ROOT/mail"
|
||||||
chown root.www-data $STORAGE_ROOT/mail/users.sqlite
|
chown root:www-data "$STORAGE_ROOT/mail/users.sqlite"
|
||||||
chmod 664 $STORAGE_ROOT/mail/users.sqlite
|
chmod 664 "$STORAGE_ROOT/mail/users.sqlite"
|
||||||
|
|
||||||
# Fix Carddav permissions:
|
# Fix Carddav permissions:
|
||||||
chown -f -R root.www-data ${RCM_PLUGIN_DIR}/carddav
|
chown -f -R root:www-data ${RCM_PLUGIN_DIR}/carddav
|
||||||
# root.www-data need all permissions, others only read
|
# root:www-data need all permissions, others only read
|
||||||
chmod -R 774 ${RCM_PLUGIN_DIR}/carddav
|
chmod -R 774 ${RCM_PLUGIN_DIR}/carddav
|
||||||
|
|
||||||
# Run Roundcube database migration script (database is created if it does not exist)
|
# 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
|
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
|
chown www-data:www-data "$STORAGE_ROOT/mail/roundcube/roundcube.sqlite"
|
||||||
chmod 664 $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)
|
||||||
|
sed -i.miabold 's/^[^#]\+.\+PRAGMA journal_mode = WAL.\+$/#&/' \
|
||||||
|
/usr/local/lib/roundcubemail/program/lib/Roundcube/db/sqlite.php
|
||||||
|
|
||||||
|
# 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
|
||||||
|
sqlite3 "$STORAGE_ROOT/mail/roundcube/roundcube.sqlite" 'PRAGMA journal_mode=WAL;'
|
||||||
|
|
||||||
# Enable PHP modules.
|
# Enable PHP modules.
|
||||||
phpenmod -v $PHP_VER imap
|
phpenmod -v "$PHP_VER" imap
|
||||||
restart_service php$PHP_VER-fpm
|
restart_service php"$PHP_VER"-fpm
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ source /etc/mailinabox.conf # load global vars
|
|||||||
|
|
||||||
echo "Installing Z-Push (Exchange/ActiveSync server)..."
|
echo "Installing Z-Push (Exchange/ActiveSync server)..."
|
||||||
apt_install \
|
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.
|
# Copy Z-Push into place.
|
||||||
VERSION=2.6.2
|
VERSION=2.7.1
|
||||||
TARGETHASH=f0e8091a8030e5b851f5ba1f9f0e1a05b8762d80
|
TARGETHASH=f15c566b1ad50de24f3f08f505f0c3d8155c2d0d
|
||||||
needs_update=0 #NODOC
|
needs_update=0 #NODOC
|
||||||
if [ ! -f /usr/local/lib/z-push/version ]; then
|
if [ ! -f /usr/local/lib/z-push/version ]; then
|
||||||
needs_update=1 #NODOC
|
needs_update=1 #NODOC
|
||||||
@@ -41,7 +41,15 @@ if [ $needs_update == 1 ]; then
|
|||||||
mv /tmp/z-push/*/src /usr/local/lib/z-push
|
mv /tmp/z-push/*/src /usr/local/lib/z-push
|
||||||
rm -rf /tmp/z-push.zip /tmp/z-push
|
rm -rf /tmp/z-push.zip /tmp/z-push
|
||||||
|
|
||||||
|
# Create admin and top scripts with PHP_VER
|
||||||
rm -f /usr/sbin/z-push-{admin,top}
|
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
|
||||||
|
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
|
||||||
|
chmod 755 /usr/sbin/z-push-top
|
||||||
|
|
||||||
echo $VERSION > /usr/local/lib/z-push/version
|
echo $VERSION > /usr/local/lib/z-push/version
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -100,8 +108,8 @@ EOF
|
|||||||
|
|
||||||
# Restart service.
|
# Restart service.
|
||||||
|
|
||||||
restart_service php$PHP_VER-fpm
|
restart_service php"$PHP_VER"-fpm
|
||||||
|
|
||||||
# Fix states after upgrade
|
# 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
|
# Use this script to make an archive of the contents of all
|
||||||
# of the configuration files we edit with editconf.py.
|
# 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 ======================================================================
|
||||||
echo $fn
|
echo "$fn"
|
||||||
echo ======================================================================
|
echo ======================================================================
|
||||||
cat $fn
|
cat "$fn"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ POSTDATA=dummy
|
|||||||
if [ "$1" == "--force" ]; then
|
if [ "$1" == "--force" ]; then
|
||||||
POSTDATA=force=1
|
POSTDATA=force=1
|
||||||
fi
|
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
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ for setting in settings:
|
|||||||
|
|
||||||
found = set()
|
found = set()
|
||||||
buf = ""
|
buf = ""
|
||||||
input_lines = list(open(filename))
|
with open(filename, "r") as f:
|
||||||
|
input_lines = list(f)
|
||||||
|
|
||||||
while len(input_lines) > 0:
|
while len(input_lines) > 0:
|
||||||
line = input_lines.pop(0)
|
line = input_lines.pop(0)
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ if [ -z "$1" ]; then
|
|||||||
echo
|
echo
|
||||||
echo "Available backups:"
|
echo "Available backups:"
|
||||||
echo
|
echo
|
||||||
find $STORAGE_ROOT/owncloud-backup/* -maxdepth 0 -type d
|
find "$STORAGE_ROOT/owncloud-backup/"* -maxdepth 0 -type d
|
||||||
echo
|
echo
|
||||||
echo "Supply the directory that was created during the last installation as the only commandline argument"
|
echo "Supply the directory that was created during the last installation as the only commandline argument"
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -f $1/config.php ]; then
|
if [ ! -f "$1/config.php" ]; then
|
||||||
echo "This isn't a valid backup location"
|
echo "This isn't a valid backup location"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -36,14 +36,14 @@ cp -r "$1/owncloud-install" /usr/local/lib/owncloud
|
|||||||
# restore access rights
|
# restore access rights
|
||||||
chmod 750 /usr/local/lib/owncloud/{apps,config}
|
chmod 750 /usr/local/lib/owncloud/{apps,config}
|
||||||
|
|
||||||
cp "$1/owncloud.db" $STORAGE_ROOT/owncloud/
|
cp "$1/owncloud.db" "$STORAGE_ROOT/owncloud/"
|
||||||
cp "$1/config.php" $STORAGE_ROOT/owncloud/
|
cp "$1/config.php" "$STORAGE_ROOT/owncloud/"
|
||||||
|
|
||||||
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
|
||||||
chown -f -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
|
chown -f -R www-data:www-data "$STORAGE_ROOT/owncloud" /usr/local/lib/owncloud
|
||||||
chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
|
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
|
service php8.0-fpm start
|
||||||
echo "Done"
|
echo "Done"
|
||||||
|
|||||||
@@ -9,15 +9,15 @@
|
|||||||
source /etc/mailinabox.conf # load global vars
|
source /etc/mailinabox.conf # load global vars
|
||||||
|
|
||||||
ADMIN=$(./mail.py user admins | head -n 1)
|
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 "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 "You can provide another user to unlock as the first argument of this script."
|
||||||
echo
|
echo
|
||||||
echo WARNING: you could break mail-in-a-box when fiddling around with Nextcloud\'s admin interface
|
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 "If in doubt, press CTRL-C to cancel."
|
||||||
echo
|
echo
|
||||||
echo Press enter to continue.
|
echo "Press enter to continue."
|
||||||
read
|
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."
|
||||||
|
|||||||
@@ -17,13 +17,8 @@ accesses = set()
|
|||||||
# Scan the current and rotated access logs.
|
# Scan the current and rotated access logs.
|
||||||
for fn in glob.glob("/var/log/nginx/access.log*"):
|
for fn in glob.glob("/var/log/nginx/access.log*"):
|
||||||
# Gunzip if necessary.
|
# Gunzip if necessary.
|
||||||
if fn.endswith(".gz"):
|
|
||||||
f = gzip.open(fn)
|
|
||||||
else:
|
|
||||||
f = open(fn, "rb")
|
|
||||||
|
|
||||||
# Loop through the lines in the access log.
|
# Loop through the lines in the access log.
|
||||||
with f:
|
with (gzip.open if fn.endswith(".gz") else open)(fn, "rb") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
# Find lines that are GETs on the bootstrap script by either curl or wget.
|
# Find lines that are GETs on the bootstrap script by either curl or wget.
|
||||||
# (Note that we purposely skip ...?ping=1 requests which is the admin panel querying us for updates.)
|
# (Note that we purposely skip ...?ping=1 requests which is the admin panel querying us for updates.)
|
||||||
@@ -43,7 +38,8 @@ for date, ip in accesses:
|
|||||||
# Since logs are rotated, store the statistics permanently in a JSON file.
|
# Since logs are rotated, store the statistics permanently in a JSON file.
|
||||||
# Load in the stats from an existing file.
|
# Load in the stats from an existing file.
|
||||||
if os.path.exists(outfn):
|
if os.path.exists(outfn):
|
||||||
existing_data = json.load(open(outfn))
|
with open(outfn, "r") as f:
|
||||||
|
existing_data = json.load(f)
|
||||||
for date, count in existing_data:
|
for date, count in existing_data:
|
||||||
if date not in by_date:
|
if date not in by_date:
|
||||||
by_date[date] = count
|
by_date[date] = count
|
||||||
|
|||||||
@@ -124,13 +124,14 @@ def generate_documentation():
|
|||||||
""")
|
""")
|
||||||
|
|
||||||
parser = Source.parser()
|
parser = Source.parser()
|
||||||
for line in open("setup/start.sh"):
|
with open("setup/start.sh", "r") as start_file:
|
||||||
try:
|
for line in start_file:
|
||||||
fn = parser.parse_string(line).filename()
|
try:
|
||||||
except:
|
fn = parser.parse_string(line).filename()
|
||||||
continue
|
except:
|
||||||
if fn in ("setup/start.sh", "setup/preflight.sh", "setup/questions.sh", "setup/firstuser.sh", "setup/management.sh"):
|
continue
|
||||||
continue
|
if fn in ("setup/start.sh", "setup/preflight.sh", "setup/questions.sh", "setup/firstuser.sh", "setup/management.sh"):
|
||||||
|
continue
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
print(fn, file=sys.stderr)
|
print(fn, file=sys.stderr)
|
||||||
@@ -401,7 +402,8 @@ class BashScript(Grammar):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def parse(fn):
|
def parse(fn):
|
||||||
if fn in ("setup/functions.sh", "/etc/mailinabox.conf"): return ""
|
if fn in ("setup/functions.sh", "/etc/mailinabox.conf"): return ""
|
||||||
string = open(fn).read()
|
with open(fn, "r") as f:
|
||||||
|
string = f.read()
|
||||||
|
|
||||||
# tokenize
|
# tokenize
|
||||||
string = re.sub(".* #NODOC\n", "", string)
|
string = re.sub(".* #NODOC\n", "", string)
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/bash
|
#!/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