mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2026-03-12 17:07:23 +01:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a13fd90347 | ||
|
|
18f1689f45 | ||
|
|
8234a5a9f4 | ||
|
|
1d9f9ea617 | ||
|
|
fbb38c3881 | ||
|
|
2caddb41eb | ||
|
|
d2b7204319 | ||
|
|
68ebca8a15 | ||
|
|
9c9dcdbf0a | ||
|
|
0c4c2e51bb | ||
|
|
828512b95a | ||
|
|
add985ce5d | ||
|
|
416dbebf45 | ||
|
|
2a046a22f4 | ||
|
|
b66f12dd4c | ||
|
|
6e04eb490f | ||
|
|
cd39c2b53f | ||
|
|
5da168466d | ||
|
|
a5f39784dd | ||
|
|
a072730fb8 | ||
|
|
00c61dbcdd | ||
|
|
10bf40250b | ||
|
|
453091f1fb | ||
|
|
48e0f39179 | ||
|
|
bb641cdfba | ||
|
|
255a65ac98 | ||
|
|
c7badb80d1 | ||
|
|
653cb7ce10 | ||
|
|
d7d8964afc | ||
|
|
6c3696a54a | ||
|
|
9c9cae2096 | ||
|
|
423f1907d0 | ||
|
|
86621392f6 | ||
|
|
368b9c50d0 | ||
|
|
3830facf78 | ||
|
|
d4baac2363 | ||
|
|
f88c907a29 | ||
|
|
89222d519a | ||
|
|
36bef2ee16 | ||
|
|
f6b20a810f | ||
|
|
f2ff14100e | ||
|
|
2c86fa3755 | ||
|
|
3c05fc94ff | ||
|
|
2e00530944 | ||
|
|
32d6728dc9 | ||
|
|
a3c71fe14f | ||
|
|
a24977a96e | ||
|
|
e694f57673 | ||
|
|
cd59de6314 | ||
|
|
a081d04082 | ||
|
|
09577816f8 | ||
|
|
2647febbf5 | ||
|
|
bd0635728c | ||
|
|
584cfe42c4 | ||
|
|
41601a592f | ||
|
|
18c253eeda | ||
|
|
34d58fb720 | ||
|
|
99d0afd650 | ||
|
|
cd717ec94e | ||
|
|
0b7f477b96 | ||
|
|
ab2367e98a | ||
|
|
384c3b5e3d | ||
|
|
d91368c478 | ||
|
|
61105b1ec3 | ||
|
|
b6f90e10c1 | ||
|
|
3af5e55035 |
30
.editorconfig
Normal file
30
.editorconfig
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# EditorConfig helps developers define and maintain consistent
|
||||||
|
# coding styles between different editors and IDEs
|
||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tabs
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[Vagrantfile]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.rb]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.py]
|
||||||
|
indent_style = tabs
|
||||||
|
|
||||||
|
[*.js]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ management/__pycache__/
|
|||||||
tools/__pycache__/
|
tools/__pycache__/
|
||||||
externals/
|
externals/
|
||||||
.env
|
.env
|
||||||
|
.vagrant
|
||||||
|
|||||||
72
CHANGELOG.md
72
CHANGELOG.md
@@ -1,6 +1,78 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
v0.23 (May 30, 2017)
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Mail:
|
||||||
|
|
||||||
|
* The default theme for Roundcube was changed to the nicer Larry theme.
|
||||||
|
* Exchange/ActiveSync support has been replaced with z-push 2.3.6 from z-push.org (rather than z-push-contrib).
|
||||||
|
|
||||||
|
ownCloud (now Nextcloud):
|
||||||
|
|
||||||
|
* ownCloud is replaced with Nextcloud 10.0.5.
|
||||||
|
* Fixed an error in Owncloud/Nextcloud setup not updating domain when changing hostname.
|
||||||
|
|
||||||
|
Control Panel/Management:
|
||||||
|
|
||||||
|
* Fix an error in the control panel showing rsync backup status.
|
||||||
|
* Fix an error in the control panel related to IPv6 addresses.
|
||||||
|
* TLS certificates for internationalized domain names can now be provisioned from Let's Encrypt automatically.
|
||||||
|
* Third-party assets used in the control panel (jQuery/Bootstrap) are now downloaded during setup and served from the box rather than from a CDN.
|
||||||
|
|
||||||
|
DNS:
|
||||||
|
|
||||||
|
* Add support for custom CAA records.
|
||||||
|
|
||||||
|
v0.22 (April 2, 2017)
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Mail:
|
||||||
|
|
||||||
|
* The CardDAV plugin has been added to Roundcube so that your ownCloud contacts are available in webmail.
|
||||||
|
* Upgraded to Roundcube 1.2.4 and updated the persistent login plugin.
|
||||||
|
* Allow larger messages to be checked by SpamAssassin.
|
||||||
|
* Dovecot's vsz memory limit has been increased proportional to system memory.
|
||||||
|
* Newly set user passwords must be at least eight characters.
|
||||||
|
|
||||||
|
ownCloud:
|
||||||
|
|
||||||
|
* Upgraded to ownCloud 9.1.4.
|
||||||
|
|
||||||
|
Control Panel/Management:
|
||||||
|
|
||||||
|
* The status checks page crashed when the mailinabox.email website was down - that's fixed.
|
||||||
|
* Made nightly re-provisioning of TLS certificates less noisy.
|
||||||
|
* Fixed bugs in rsync backup method and in the list of recent backups.
|
||||||
|
* Fixed incorrect status checks errors about IPv6 addresses.
|
||||||
|
* Fixed incorrect status checks errors for secondary nameservers if round-robin custom A records are set.
|
||||||
|
* The management mail_log.py tool has been rewritten.
|
||||||
|
|
||||||
|
DNS:
|
||||||
|
|
||||||
|
* Added support for DSA, ED25519, and custom SSHFP records.
|
||||||
|
|
||||||
|
System:
|
||||||
|
|
||||||
|
* The SSH fail2ban jail was not activated.
|
||||||
|
|
||||||
|
Installation:
|
||||||
|
|
||||||
|
* At the end of installation, the SHA256 -- rather than SHA1 -- hash of the system's TLS certificate is shown.
|
||||||
|
|
||||||
|
v0.21c (February 1, 2017)
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Installations and upgrades started failing about 10 days ago with the error "ImportError: No module named 'packaging'" after an upstream package (Python's setuptools) was updated by its maintainers. The updated package conflicted with Ubuntu 14.04's version of another package (Python's pip). This update upgrades both packages to remove the conflict.
|
||||||
|
|
||||||
|
If you already encountered the error during installation or upgrade of Mail-in-a-Box, this update may not correct the problem on your existing system. See https://discourse.mailinabox.email/t/v0-21c-release-fixes-python-package-installation-issue/1881 for help if the problem persists after upgrading to this version of Mail-in-a-Box.
|
||||||
|
|
||||||
|
v0.21b (December 4, 2016)
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
This update corrects a first-time installation issue introduced in v0.21 caused by the new Exchange/ActiveSync feature.
|
||||||
|
|
||||||
v0.21 (November 30, 2016)
|
v0.21 (November 30, 2016)
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ It is a one-click email appliance. There are no user-configurable setup options.
|
|||||||
|
|
||||||
The components installed are:
|
The components installed are:
|
||||||
|
|
||||||
* SMTP ([postfix](http://www.postfix.org/)), IMAP ([dovecot](http://dovecot.org/)), CardDAV/CalDAV ([ownCloud](https://owncloud.org/)), Exchange ActiveSync ([z-push](https://github.com/fmbiete/Z-Push-contrib))
|
* SMTP ([postfix](http://www.postfix.org/)), IMAP ([dovecot](http://dovecot.org/)), CardDAV/CalDAV ([Nextcloud](https://nextcloud.com/)), Exchange ActiveSync ([z-push](http://z-push.org/))
|
||||||
* Webmail ([Roundcube](http://roundcube.net/)), static website hosting ([nginx](http://nginx.org/))
|
* Webmail ([Roundcube](http://roundcube.net/)), static website hosting ([nginx](http://nginx.org/))
|
||||||
* Spam filtering ([spamassassin](https://spamassassin.apache.org/)), greylisting ([postgrey](http://postgrey.schweikert.ch/))
|
* Spam filtering ([spamassassin](https://spamassassin.apache.org/)), greylisting ([postgrey](http://postgrey.schweikert.ch/))
|
||||||
* DNS ([nsd4](https://www.nlnetlabs.nl/projects/nsd/)) with [SPF](https://en.wikipedia.org/wiki/Sender_Policy_Framework), DKIM ([OpenDKIM](http://www.opendkim.org/)), [DMARC](https://en.wikipedia.org/wiki/DMARC), [DNSSEC](https://en.wikipedia.org/wiki/DNSSEC), [DANE TLSA](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities), and [SSHFP](https://tools.ietf.org/html/rfc4255) records automatically set
|
* DNS ([nsd4](https://www.nlnetlabs.nl/projects/nsd/)) with [SPF](https://en.wikipedia.org/wiki/Sender_Policy_Framework), DKIM ([OpenDKIM](http://www.opendkim.org/)), [DMARC](https://en.wikipedia.org/wiki/DMARC), [DNSSEC](https://en.wikipedia.org/wiki/DNSSEC), [DANE TLSA](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities), and [SSHFP](https://tools.ietf.org/html/rfc4255) records automatically set
|
||||||
@@ -59,7 +59,7 @@ by me:
|
|||||||
$ curl -s https://keybase.io/joshdata/key.asc | gpg --import
|
$ curl -s https://keybase.io/joshdata/key.asc | gpg --import
|
||||||
gpg: key C10BDD81: public key "Joshua Tauberer <jt@occams.info>" imported
|
gpg: key C10BDD81: public key "Joshua Tauberer <jt@occams.info>" imported
|
||||||
|
|
||||||
$ git verify-tag v0.21
|
$ git verify-tag v0.23
|
||||||
gpg: Signature made ..... using RSA key ID C10BDD81
|
gpg: Signature made ..... using RSA key ID C10BDD81
|
||||||
gpg: Good signature from "Joshua Tauberer <jt@occams.info>"
|
gpg: Good signature from "Joshua Tauberer <jt@occams.info>"
|
||||||
gpg: WARNING: This key is not certified with a trusted signature!
|
gpg: WARNING: This key is not certified with a trusted signature!
|
||||||
@@ -72,7 +72,7 @@ and on my [personal homepage](https://razor.occams.info/). (Of course, if this r
|
|||||||
|
|
||||||
Checkout the tag corresponding to the most recent release:
|
Checkout the tag corresponding to the most recent release:
|
||||||
|
|
||||||
$ git checkout v0.21
|
$ git checkout v0.23
|
||||||
|
|
||||||
Begin the installation.
|
Begin the installation.
|
||||||
|
|
||||||
|
|||||||
14
Vagrantfile
vendored
14
Vagrantfile
vendored
@@ -5,23 +5,27 @@ Vagrant.configure("2") do |config|
|
|||||||
config.vm.box = "ubuntu14.04"
|
config.vm.box = "ubuntu14.04"
|
||||||
config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box"
|
config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box"
|
||||||
|
|
||||||
|
if Vagrant.has_plugin?("vagrant-cachier")
|
||||||
|
# Configure cached packages to be shared between instances of the same base box.
|
||||||
|
# More info on http://fgrehm.viewdocs.io/vagrant-cachier/usage
|
||||||
|
config.cache.scope = :box
|
||||||
|
end
|
||||||
|
|
||||||
# Network config: Since it's a mail server, the machine must be connected
|
# Network config: Since it's a mail server, the machine must be connected
|
||||||
# to the public web. However, we currently don't want to expose SSH since
|
# to the public web. However, we currently don't want to expose SSH since
|
||||||
# the machine's box will let anyone log into it. So instead we'll put the
|
# the machine's box will let anyone log into it. So instead we'll put the
|
||||||
# machine on a private network.
|
# machine on a private network.
|
||||||
config.vm.hostname = "mailinabox"
|
config.vm.hostname = "mailinabox.lan"
|
||||||
config.vm.network "private_network", ip: "192.168.50.4"
|
config.vm.network "private_network", ip: "192.168.50.4"
|
||||||
|
|
||||||
config.vm.provision :shell, :inline => <<-SH
|
config.vm.provision :shell, :inline => <<-SH
|
||||||
# Set environment variables so that the setup script does
|
# Set environment variables so that the setup script does
|
||||||
# not ask any questions during provisioning. We'll let the
|
# not ask any questions during provisioning. We'll let the
|
||||||
# machine figure out its own public IP and it'll take a
|
# machine figure out its own public IP.
|
||||||
# subdomain on our justtesting.email domain so we can get
|
|
||||||
# started quickly.
|
|
||||||
export NONINTERACTIVE=1
|
export NONINTERACTIVE=1
|
||||||
export PUBLIC_IP=auto
|
export PUBLIC_IP=auto
|
||||||
export PUBLIC_IPV6=auto
|
export PUBLIC_IPV6=auto
|
||||||
export PRIMARY_HOSTNAME=auto-easy
|
export PRIMARY_HOSTNAME=auto
|
||||||
#export SKIP_NETWORK_CHECKS=1
|
#export SKIP_NETWORK_CHECKS=1
|
||||||
|
|
||||||
# Start the setup script.
|
# Start the setup script.
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ findtime = 30
|
|||||||
enabled = true
|
enabled = true
|
||||||
port = http,https
|
port = http,https
|
||||||
filter = miab-owncloud
|
filter = miab-owncloud
|
||||||
logpath = STORAGE_ROOT/owncloud/owncloud.log
|
logpath = STORAGE_ROOT/owncloud/nextcloud.log
|
||||||
maxretry = 20
|
maxretry = 20
|
||||||
findtime = 120
|
findtime = 120
|
||||||
|
|
||||||
@@ -73,6 +73,7 @@ action = iptables-allports[name=recidive]
|
|||||||
enabled = true
|
enabled = true
|
||||||
|
|
||||||
[ssh]
|
[ssh]
|
||||||
|
enabled = true
|
||||||
maxretry = 7
|
maxretry = 7
|
||||||
bantime = 3600
|
bantime = 3600
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
# takes precedence over all non-regex matches and only regex matches that
|
# takes precedence over all non-regex matches and only regex matches that
|
||||||
# come after it (i.e. none of those, since this is the last one.) That means
|
# come after it (i.e. none of those, since this is the last one.) That means
|
||||||
# we're blocking dotfiles in the static hosted sites but not the FastCGI-
|
# we're blocking dotfiles in the static hosted sites but not the FastCGI-
|
||||||
# handled locations for ownCloud (which serves user-uploaded files that might
|
# handled locations for Nextcloud (which serves user-uploaded files that might
|
||||||
# have this pattern, see #414) or some of the other services.
|
# have this pattern, see #414) or some of the other services.
|
||||||
location ~ /\.(ht|svn|git|hg|bzr) {
|
location ~ /\.(ht|svn|git|hg|bzr) {
|
||||||
log_not_found off;
|
log_not_found off;
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
# Control Panel
|
# Control Panel
|
||||||
# Proxy /admin to our Python based control panel daemon. It is
|
# Proxy /admin to our Python based control panel daemon. It is
|
||||||
# listening on IPv4 only so use an IP address and not 'localhost'.
|
# listening on IPv4 only so use an IP address and not 'localhost'.
|
||||||
|
location /admin/assets {
|
||||||
|
alias /usr/local/lib/mailinabox/vendor/assets;
|
||||||
|
}
|
||||||
rewrite ^/admin$ /admin/;
|
rewrite ^/admin$ /admin/;
|
||||||
rewrite ^/admin/munin$ /admin/munin/ redirect;
|
rewrite ^/admin/munin$ /admin/munin/ redirect;
|
||||||
location /admin/ {
|
location /admin/ {
|
||||||
@@ -12,7 +15,7 @@
|
|||||||
add_header Strict-Transport-Security max-age=31536000;
|
add_header Strict-Transport-Security max-age=31536000;
|
||||||
}
|
}
|
||||||
|
|
||||||
# ownCloud configuration.
|
# Nextcloud configuration.
|
||||||
rewrite ^/cloud$ /cloud/ redirect;
|
rewrite ^/cloud$ /cloud/ redirect;
|
||||||
rewrite ^/cloud/$ /cloud/index.php;
|
rewrite ^/cloud/$ /cloud/index.php;
|
||||||
rewrite ^/cloud/(contacts|calendar|files)$ /cloud/index.php/apps/$1/ redirect;
|
rewrite ^/cloud/(contacts|calendar|files)$ /cloud/index.php/apps/$1/ redirect;
|
||||||
@@ -41,13 +44,11 @@
|
|||||||
fastcgi_param MOD_X_ACCEL_REDIRECT_PREFIX /owncloud-xaccel;
|
fastcgi_param MOD_X_ACCEL_REDIRECT_PREFIX /owncloud-xaccel;
|
||||||
fastcgi_read_timeout 630;
|
fastcgi_read_timeout 630;
|
||||||
fastcgi_pass php-fpm;
|
fastcgi_pass php-fpm;
|
||||||
error_page 403 /cloud/core/templates/403.php;
|
|
||||||
error_page 404 /cloud/core/templates/404.php;
|
|
||||||
client_max_body_size 1G;
|
client_max_body_size 1G;
|
||||||
fastcgi_buffers 64 4K;
|
fastcgi_buffers 64 4K;
|
||||||
}
|
}
|
||||||
location ^~ /owncloud-xaccel/ {
|
location ^~ /owncloud-xaccel/ {
|
||||||
# This directory is for MOD_X_ACCEL_REDIRECT_ENABLED. ownCloud sends the full file
|
# This directory is for MOD_X_ACCEL_REDIRECT_ENABLED. Nextcloud sends the full file
|
||||||
# path on disk as a subdirectory under this virtual path.
|
# path on disk as a subdirectory under this virtual path.
|
||||||
# We must only allow 'internal' redirects within nginx so that the filesystem
|
# We must only allow 'internal' redirects within nginx so that the filesystem
|
||||||
# is not exposed to the world.
|
# is not exposed to the world.
|
||||||
|
|||||||
@@ -5,11 +5,12 @@
|
|||||||
* Descr : Autodiscover configuration file
|
* Descr : Autodiscover configuration file
|
||||||
************************************************/
|
************************************************/
|
||||||
|
|
||||||
|
define('TIMEZONE', '');
|
||||||
|
|
||||||
// Defines the base path on the server
|
// Defines the base path on the server
|
||||||
define('BASE_PATH', dirname($_SERVER['SCRIPT_FILENAME']). '/');
|
define('BASE_PATH', dirname($_SERVER['SCRIPT_FILENAME']). '/');
|
||||||
|
|
||||||
// The Z-Push server location for the autodiscover response
|
define('ZPUSH_HOST', 'PRIMARY_HOSTNAME');
|
||||||
define('SERVERURL', 'https://PRIMARY_HOSTNAME/Microsoft-Server-ActiveSync');
|
|
||||||
|
|
||||||
define('USE_FULLEMAIL_FOR_LOGIN', true);
|
define('USE_FULLEMAIL_FOR_LOGIN', true);
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ define('LOGFILE', LOGFILEDIR . 'autodiscover.log');
|
|||||||
define('LOGERRORFILE', LOGFILEDIR . 'autodiscover-error.log');
|
define('LOGERRORFILE', LOGFILEDIR . 'autodiscover-error.log');
|
||||||
define('LOGLEVEL', LOGLEVEL_INFO);
|
define('LOGLEVEL', LOGLEVEL_INFO);
|
||||||
define('LOGUSERLEVEL', LOGLEVEL);
|
define('LOGUSERLEVEL', LOGLEVEL);
|
||||||
|
$specialLogUsers = array();
|
||||||
|
|
||||||
// the backend data provider
|
// the backend data provider
|
||||||
define('BACKEND_PROVIDER', 'BackendCombined');
|
define('BACKEND_PROVIDER', 'BackendCombined');
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ define('CARDDAV_CONTACTS_FOLDER_NAME', '%u Addressbook');
|
|||||||
define('CARDDAV_SUPPORTS_SYNC', false);
|
define('CARDDAV_SUPPORTS_SYNC', false);
|
||||||
|
|
||||||
// If the CardDAV server supports the FN attribute for searches
|
// If the CardDAV server supports the FN attribute for searches
|
||||||
// DAViCal supports it, but SabreDav, Owncloud and SOGo don't
|
// DAViCal supports it, but SabreDav, Nextcloud and SOGo don't
|
||||||
// Setting this to true will search by FN. If false will search by sn, givenName and email
|
// Setting this to true will search by FN. If false will search by sn, givenName and email
|
||||||
// It's safe to leave it as false
|
// It's safe to leave it as false
|
||||||
define('CARDDAV_SUPPORTS_FN_SEARCH', false);
|
define('CARDDAV_SUPPORTS_FN_SEARCH', false);
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ define('IMAP_FOLDER_TRASH', 'TRASH');
|
|||||||
define('IMAP_FOLDER_SPAM', 'SPAM');
|
define('IMAP_FOLDER_SPAM', 'SPAM');
|
||||||
define('IMAP_FOLDER_ARCHIVE', 'ARCHIVE');
|
define('IMAP_FOLDER_ARCHIVE', 'ARCHIVE');
|
||||||
|
|
||||||
|
define('IMAP_INLINE_FORWARD', true);
|
||||||
|
define('IMAP_EXCLUDED_FOLDERS', '');
|
||||||
|
|
||||||
define('IMAP_FROM_SQL_DSN', 'sqlite:STORAGE_ROOT/mail/roundcube/roundcube.sqlite');
|
define('IMAP_FROM_SQL_DSN', 'sqlite:STORAGE_ROOT/mail/roundcube/roundcube.sqlite');
|
||||||
define('IMAP_FROM_SQL_USER', '');
|
define('IMAP_FROM_SQL_USER', '');
|
||||||
define('IMAP_FROM_SQL_PASSWORD', '');
|
define('IMAP_FROM_SQL_PASSWORD', '');
|
||||||
@@ -49,5 +52,6 @@ global $imap_smtp_params;
|
|||||||
$imap_smtp_params = array('host' => 'ssl://127.0.0.1', 'port' => 587, 'auth' => true, 'username' => 'imap_username', 'password' => 'imap_password');
|
$imap_smtp_params = array('host' => 'ssl://127.0.0.1', 'port' => 587, 'auth' => true, 'username' => 'imap_username', 'password' => 'imap_password');
|
||||||
|
|
||||||
define('MAIL_MIMEPART_CRLF', "\r\n");
|
define('MAIL_MIMEPART_CRLF', "\r\n");
|
||||||
|
define('IMAP_MEETING_USE_CALDAV', true);
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -10,8 +10,9 @@
|
|||||||
import os, os.path, shutil, glob, re, datetime, sys
|
import os, os.path, shutil, glob, re, datetime, sys
|
||||||
import dateutil.parser, dateutil.relativedelta, dateutil.tz
|
import dateutil.parser, dateutil.relativedelta, dateutil.tz
|
||||||
import rtyaml
|
import rtyaml
|
||||||
|
from exclusiveprocess import Lock
|
||||||
|
|
||||||
from utils import exclusive_process, load_environment, shell, wait_for_service, fix_boto
|
from utils import load_environment, shell, wait_for_service, fix_boto
|
||||||
|
|
||||||
rsync_ssh_options = [
|
rsync_ssh_options = [
|
||||||
"--ssh-options='-i /root/.ssh/id_rsa_miab'",
|
"--ssh-options='-i /root/.ssh/id_rsa_miab'",
|
||||||
@@ -38,6 +39,8 @@ def backup_status(env):
|
|||||||
def reldate(date, ref, clip):
|
def reldate(date, ref, clip):
|
||||||
if ref < date: return clip
|
if ref < date: return clip
|
||||||
rd = dateutil.relativedelta.relativedelta(ref, date)
|
rd = dateutil.relativedelta.relativedelta(ref, date)
|
||||||
|
if rd.years > 1: return "%d years, %d months" % (rd.years, rd.months)
|
||||||
|
if rd.years == 1: return "%d year, %d months" % (rd.years, rd.months)
|
||||||
if rd.months > 1: return "%d months, %d days" % (rd.months, rd.days)
|
if rd.months > 1: return "%d months, %d days" % (rd.months, rd.days)
|
||||||
if rd.months == 1: return "%d month, %d days" % (rd.months, rd.days)
|
if rd.months == 1: return "%d month, %d days" % (rd.months, rd.days)
|
||||||
if rd.days >= 7: return "%d days" % rd.days
|
if rd.days >= 7: return "%d days" % rd.days
|
||||||
@@ -112,7 +115,7 @@ def backup_status(env):
|
|||||||
# full backup. That full backup frees up this one to be deleted. But, the backup
|
# full backup. That full backup frees up this one to be deleted. But, the backup
|
||||||
# must also be at least min_age_in_days old too.
|
# must also be at least min_age_in_days old too.
|
||||||
deleted_in = None
|
deleted_in = None
|
||||||
if incremental_count > 0 and first_full_size is not None:
|
if incremental_count > 0 and incremental_size > 0 and first_full_size is not None:
|
||||||
# How many days until the next incremental backup? First, the part of
|
# How many days until the next incremental backup? First, the part of
|
||||||
# the algorithm based on increment sizes:
|
# the algorithm based on increment sizes:
|
||||||
est_days_to_next_full = (.5 * first_full_size - incremental_size) / (incremental_size/incremental_count)
|
est_days_to_next_full = (.5 * first_full_size - incremental_size) / (incremental_size/incremental_count)
|
||||||
@@ -204,7 +207,10 @@ def get_target_type(config):
|
|||||||
def perform_backup(full_backup):
|
def perform_backup(full_backup):
|
||||||
env = load_environment()
|
env = load_environment()
|
||||||
|
|
||||||
exclusive_process("backup")
|
# Create an global exclusive lock so that the backup script
|
||||||
|
# cannot be run more than one.
|
||||||
|
Lock(die=True).forever()
|
||||||
|
|
||||||
config = get_backup_config(env)
|
config = get_backup_config(env)
|
||||||
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
|
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
|
||||||
backup_cache_dir = os.path.join(backup_root, 'cache')
|
backup_cache_dir = os.path.join(backup_root, 'cache')
|
||||||
@@ -382,21 +388,22 @@ def run_duplicity_restore(args):
|
|||||||
def list_target_files(config):
|
def list_target_files(config):
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
try:
|
try:
|
||||||
p = urllib.parse.urlparse(config["target"])
|
target = urllib.parse.urlparse(config["target"])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return "invalid target"
|
return "invalid target"
|
||||||
|
|
||||||
if p.scheme == "file":
|
if target.scheme == "file":
|
||||||
return [(fn, os.path.getsize(os.path.join(p.path, fn))) for fn in os.listdir(p.path)]
|
return [(fn, os.path.getsize(os.path.join(target.path, fn))) for fn in os.listdir(target.path)]
|
||||||
|
|
||||||
elif p.scheme == "rsync":
|
elif target.scheme == "rsync":
|
||||||
rsync_fn_size_re = re.compile(r'.* ([^ ]*) [^ ]* [^ ]* (.*)')
|
rsync_fn_size_re = re.compile(r'.* ([^ ]*) [^ ]* [^ ]* (.*)')
|
||||||
rsync_target = '{host}:{path}'
|
rsync_target = '{host}:{path}'
|
||||||
|
|
||||||
_, target_host, target_path = config['target'].split('//')
|
target_path = target.path
|
||||||
target_path = '/' + target_path
|
|
||||||
if not target_path.endswith('/'):
|
if not target_path.endswith('/'):
|
||||||
target_path += '/'
|
target_path = target_path + '/'
|
||||||
|
if target_path.startswith('/'):
|
||||||
|
target_path = target_path[1:]
|
||||||
|
|
||||||
rsync_command = [ 'rsync',
|
rsync_command = [ 'rsync',
|
||||||
'-e',
|
'-e',
|
||||||
@@ -404,11 +411,11 @@ def list_target_files(config):
|
|||||||
'--list-only',
|
'--list-only',
|
||||||
'-r',
|
'-r',
|
||||||
rsync_target.format(
|
rsync_target.format(
|
||||||
host=target_host,
|
host=target.netloc,
|
||||||
path=target_path)
|
path=target_path)
|
||||||
]
|
]
|
||||||
|
|
||||||
code, listing = shell('check_output', rsync_command, trap=True)
|
code, listing = shell('check_output', rsync_command, trap=True, capture_stderr=True)
|
||||||
if code == 0:
|
if code == 0:
|
||||||
ret = []
|
ret = []
|
||||||
for l in listing.split('\n'):
|
for l in listing.split('\n'):
|
||||||
@@ -417,21 +424,33 @@ def list_target_files(config):
|
|||||||
ret.append( (match.groups()[1], int(match.groups()[0].replace(',',''))) )
|
ret.append( (match.groups()[1], int(match.groups()[0].replace(',',''))) )
|
||||||
return ret
|
return ret
|
||||||
else:
|
else:
|
||||||
raise ValueError("Connection to rsync host failed")
|
if 'Permission denied (publickey).' in listing:
|
||||||
|
reason = "Invalid user or check you correctly copied the SSH key."
|
||||||
|
elif 'No such file or directory' in listing:
|
||||||
|
reason = "Provided path {} is invalid.".format(target_path)
|
||||||
|
elif 'Network is unreachable' in listing:
|
||||||
|
reason = "The IP address {} is unreachable.".format(target.hostname)
|
||||||
|
elif 'Could not resolve hostname':
|
||||||
|
reason = "The hostname {} cannot be resolved.".format(target.hostname)
|
||||||
|
else:
|
||||||
|
reason = "Unknown error." \
|
||||||
|
"Please check running 'python management/backup.py --verify'" \
|
||||||
|
"from mailinabox sources to debug the issue."
|
||||||
|
raise ValueError("Connection to rsync host failed: {}".format(reason))
|
||||||
|
|
||||||
elif p.scheme == "s3":
|
elif target.scheme == "s3":
|
||||||
# match to a Region
|
# match to a Region
|
||||||
fix_boto() # must call prior to importing boto
|
fix_boto() # must call prior to importing boto
|
||||||
import boto.s3
|
import boto.s3
|
||||||
from boto.exception import BotoServerError
|
from boto.exception import BotoServerError
|
||||||
for region in boto.s3.regions():
|
for region in boto.s3.regions():
|
||||||
if region.endpoint == p.hostname:
|
if region.endpoint == target.hostname:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid S3 region/host.")
|
raise ValueError("Invalid S3 region/host.")
|
||||||
|
|
||||||
bucket = p.path[1:].split('/')[0]
|
bucket = target.path[1:].split('/')[0]
|
||||||
path = '/'.join(p.path[1:].split('/')[1:]) + '/'
|
path = '/'.join(target.path[1:].split('/')[1:]) + '/'
|
||||||
|
|
||||||
# If no prefix is specified, set the path to '', otherwise boto won't list the files
|
# If no prefix is specified, set the path to '', otherwise boto won't list the files
|
||||||
if path == '/':
|
if path == '/':
|
||||||
@@ -536,6 +555,12 @@ if __name__ == "__main__":
|
|||||||
# are readable, and b) report if they are up to date.
|
# are readable, and b) report if they are up to date.
|
||||||
run_duplicity_verification()
|
run_duplicity_verification()
|
||||||
|
|
||||||
|
elif sys.argv[-1] == "--list":
|
||||||
|
# Run duplicity's verification command to check a) the backup files
|
||||||
|
# are readable, and b) report if they are up to date.
|
||||||
|
for fn, size in list_target_files(get_backup_config(load_environment())):
|
||||||
|
print("{}\t{}".format(fn, size))
|
||||||
|
|
||||||
elif sys.argv[-1] == "--status":
|
elif sys.argv[-1] == "--status":
|
||||||
# Show backup status.
|
# Show backup status.
|
||||||
ret = backup_status(load_environment())
|
ret = backup_status(load_environment())
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export LC_TYPE=en_US.UTF-8
|
|||||||
management/backup.py | management/email_administrator.py "Backup Status"
|
management/backup.py | management/email_administrator.py "Backup Status"
|
||||||
|
|
||||||
# Provision any new certificates for new domains or domains with expiring certificates.
|
# Provision any new certificates for new domains or domains with expiring certificates.
|
||||||
management/ssl_certificates.py --headless | management/email_administrator.py "Error Provisioning TLS Certificate"
|
management/ssl_certificates.py -q --headless | management/email_administrator.py "Error Provisioning TLS Certificate"
|
||||||
|
|
||||||
# Run status checks and email the administrator if anything changed.
|
# Run status checks and email the administrator if anything changed.
|
||||||
management/status_checks.py --show-changes | management/email_administrator.py "Status Checks Change Notice"
|
management/status_checks.py --show-changes | management/email_administrator.py "Status Checks Change Notice"
|
||||||
|
|||||||
@@ -342,6 +342,7 @@ def build_sshfp_records():
|
|||||||
"ssh-rsa": 1,
|
"ssh-rsa": 1,
|
||||||
"ssh-dss": 2,
|
"ssh-dss": 2,
|
||||||
"ecdsa-sha2-nistp256": 3,
|
"ecdsa-sha2-nistp256": 3,
|
||||||
|
"ssh-ed25519": 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get our local fingerprints by running ssh-keyscan. The output looks
|
# Get our local fingerprints by running ssh-keyscan. The output looks
|
||||||
@@ -359,7 +360,7 @@ def build_sshfp_records():
|
|||||||
ports = ports + [s[1]]
|
ports = ports + [s[1]]
|
||||||
# the keys are the same at each port, so we only need to get
|
# the keys are the same at each port, so we only need to get
|
||||||
# them at the first port found (may not be port 22)
|
# them at the first port found (may not be port 22)
|
||||||
keys = shell("check_output", ["ssh-keyscan", "-p", ports[0], "localhost"])
|
keys = shell("check_output", ["ssh-keyscan", "-t", "rsa,dsa,ecdsa,ed25519", "-p", ports[0], "localhost"])
|
||||||
for key in sorted(keys.split("\n")):
|
for key in sorted(keys.split("\n")):
|
||||||
if key.strip() == "" or key[0] == "#": continue
|
if key.strip() == "" or key[0] == "#": continue
|
||||||
try:
|
try:
|
||||||
@@ -766,7 +767,7 @@ def set_custom_dns_record(qname, rtype, value, action, env):
|
|||||||
v = ipaddress.ip_address(value) # raises a ValueError if there's a problem
|
v = ipaddress.ip_address(value) # raises a ValueError if there's a problem
|
||||||
if rtype == "A" and not isinstance(v, ipaddress.IPv4Address): raise ValueError("That's an IPv6 address.")
|
if rtype == "A" and not isinstance(v, ipaddress.IPv4Address): raise ValueError("That's an IPv6 address.")
|
||||||
if rtype == "AAAA" and not isinstance(v, ipaddress.IPv6Address): raise ValueError("That's an IPv4 address.")
|
if rtype == "AAAA" and not isinstance(v, ipaddress.IPv6Address): raise ValueError("That's an IPv4 address.")
|
||||||
elif rtype in ("CNAME", "TXT", "SRV", "MX"):
|
elif rtype in ("CNAME", "TXT", "SRV", "MX", "SSHFP", "CAA"):
|
||||||
# anything goes
|
# anything goes
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@@ -881,10 +882,10 @@ def set_secondary_dns(hostnames, env):
|
|||||||
return do_dns_update(env)
|
return do_dns_update(env)
|
||||||
|
|
||||||
|
|
||||||
def get_custom_dns_record(custom_dns, qname, rtype):
|
def get_custom_dns_records(custom_dns, qname, rtype):
|
||||||
for qname1, rtype1, value in custom_dns:
|
for qname1, rtype1, value in custom_dns:
|
||||||
if qname1 == qname and rtype1 == rtype:
|
if qname1 == qname and rtype1 == rtype:
|
||||||
return value
|
yield value
|
||||||
return None
|
return None
|
||||||
|
|
||||||
########################################################################
|
########################################################################
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -599,8 +599,8 @@ def validate_password(pw):
|
|||||||
raise ValueError("No password provided.")
|
raise ValueError("No password provided.")
|
||||||
if re.search(r"[\s]", pw):
|
if re.search(r"[\s]", pw):
|
||||||
raise ValueError("Passwords cannot contain spaces.")
|
raise ValueError("Passwords cannot contain spaces.")
|
||||||
if len(pw) < 4:
|
if len(pw) < 8:
|
||||||
raise ValueError("Passwords must be at least four characters.")
|
raise ValueError("Passwords must be at least eight characters.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
import os, os.path, re, shutil
|
import os, os.path, re, shutil
|
||||||
|
|
||||||
from utils import shell, safe_domain_name, sort_domains
|
from utils import shell, safe_domain_name, sort_domains
|
||||||
|
|
||||||
import idna
|
import idna
|
||||||
|
|
||||||
# SELECTING SSL CERTIFICATES FOR USE IN WEB
|
# SELECTING SSL CERTIFICATES FOR USE IN WEB
|
||||||
@@ -214,12 +213,7 @@ def get_certificates_to_provision(env, show_extended_problems=True, force_domain
|
|||||||
|
|
||||||
# Filter out domains that we can't provision a certificate for.
|
# Filter out domains that we can't provision a certificate for.
|
||||||
def can_provision_for_domain(domain):
|
def can_provision_for_domain(domain):
|
||||||
# Let's Encrypt doesn't yet support IDNA domains.
|
from status_checks import normalize_ip
|
||||||
# We store domains in IDNA (ASCII). To see if this domain is IDNA,
|
|
||||||
# we'll see if its IDNA-decoded form is different.
|
|
||||||
if idna.decode(domain.encode("ascii")) != domain:
|
|
||||||
problems[domain] = "Let's Encrypt does not yet support provisioning certificates for internationalized domains."
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Does the domain resolve to this machine in public DNS? If not,
|
# Does the domain resolve to this machine in public DNS? If not,
|
||||||
# we can't do domain control validation. For IPv6 is configured,
|
# we can't do domain control validation. For IPv6 is configured,
|
||||||
@@ -252,7 +246,7 @@ def get_certificates_to_provision(env, show_extended_problems=True, force_domain
|
|||||||
return s
|
return s
|
||||||
# END HOTFIX
|
# END HOTFIX
|
||||||
|
|
||||||
if len(response) != 1 or rdata__str__(response[0]) != value:
|
if len(response) != 1 or normalize_ip(rdata__str__(response[0])) != normalize_ip(value):
|
||||||
problems[domain] = "Domain control validation cannot be performed for this domain because DNS points the domain to another machine (%s %s)." % (rtype, ", ".join(rdata__str__(r) for r in response))
|
problems[domain] = "Domain control validation cannot be performed for this domain because DNS points the domain to another machine (%s %s)." % (rtype, ", ".join(rdata__str__(r) for r in response))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -411,9 +405,11 @@ def provision_certificates(env, agree_to_tos_url=None, logger=None, show_extende
|
|||||||
|
|
||||||
def provision_certificates_cmdline():
|
def provision_certificates_cmdline():
|
||||||
import sys
|
import sys
|
||||||
from utils import load_environment, exclusive_process
|
from exclusiveprocess import Lock
|
||||||
|
|
||||||
exclusive_process("update_tls_certificates")
|
from utils import load_environment
|
||||||
|
|
||||||
|
Lock(die=True).forever()
|
||||||
env = load_environment()
|
env = load_environment()
|
||||||
|
|
||||||
verbose = False
|
verbose = False
|
||||||
@@ -426,7 +422,7 @@ def provision_certificates_cmdline():
|
|||||||
if args and args[0] == "-v":
|
if args and args[0] == "-v":
|
||||||
verbose = True
|
verbose = True
|
||||||
args.pop(0)
|
args.pop(0)
|
||||||
if args and args[0] == "q":
|
if args and args[0] == "-q":
|
||||||
show_extended_problems = False
|
show_extended_problems = False
|
||||||
args.pop(0)
|
args.pop(0)
|
||||||
if args and args[0] == "--headless":
|
if args and args[0] == "--headless":
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import dateutil.parser, dateutil.tz
|
|||||||
import idna
|
import idna
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
from dns_update import get_dns_zones, build_tlsa_record, get_custom_dns_config, get_secondary_dns, get_custom_dns_record
|
from dns_update import get_dns_zones, build_tlsa_record, get_custom_dns_config, get_secondary_dns, get_custom_dns_records
|
||||||
from web_update import get_web_domains, get_domains_with_a_records
|
from web_update import get_web_domains, get_domains_with_a_records
|
||||||
from ssl_certificates import get_ssl_certificates, get_domain_ssl_files, check_certificate
|
from ssl_certificates import get_ssl_certificates, get_domain_ssl_files, check_certificate
|
||||||
from mailconfig import get_mail_domains, get_mail_aliases
|
from mailconfig import get_mail_domains, get_mail_aliases
|
||||||
@@ -393,7 +393,7 @@ def check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles):
|
|||||||
|
|
||||||
# Check that PRIMARY_HOSTNAME resolves to PUBLIC_IP[V6] in public DNS.
|
# Check that PRIMARY_HOSTNAME resolves to PUBLIC_IP[V6] in public DNS.
|
||||||
ipv6 = query_dns(domain, "AAAA") if env.get("PUBLIC_IPV6") else None
|
ipv6 = query_dns(domain, "AAAA") if env.get("PUBLIC_IPV6") else None
|
||||||
if ip == env['PUBLIC_IP'] and ipv6 in (None, env['PUBLIC_IPV6']):
|
if ip == env['PUBLIC_IP'] and not (ipv6 and env['PUBLIC_IPV6'] and normalize_ip(ipv6) != normalize_ip(env['PUBLIC_IPV6'])):
|
||||||
output.print_ok("Domain resolves to box's IP address. [%s ↦ %s]" % (env['PRIMARY_HOSTNAME'], my_ips))
|
output.print_ok("Domain resolves to box's IP address. [%s ↦ %s]" % (env['PRIMARY_HOSTNAME'], my_ips))
|
||||||
else:
|
else:
|
||||||
output.print_error("""This domain must resolve to your box's IP address (%s) in public DNS but it currently resolves
|
output.print_error("""This domain must resolve to your box's IP address (%s) in public DNS but it currently resolves
|
||||||
@@ -459,7 +459,7 @@ def check_dns_zone(domain, env, output, dns_zonefiles):
|
|||||||
# half working.)
|
# half working.)
|
||||||
|
|
||||||
custom_dns_records = list(get_custom_dns_config(env)) # generator => list so we can reuse it
|
custom_dns_records = list(get_custom_dns_config(env)) # generator => list so we can reuse it
|
||||||
correct_ip = get_custom_dns_record(custom_dns_records, domain, "A") or env['PUBLIC_IP']
|
correct_ip = "; ".join(sorted(get_custom_dns_records(custom_dns_records, domain, "A"))) or env['PUBLIC_IP']
|
||||||
custom_secondary_ns = get_secondary_dns(custom_dns_records, mode="NS")
|
custom_secondary_ns = get_secondary_dns(custom_dns_records, mode="NS")
|
||||||
secondary_ns = custom_secondary_ns or ["ns2." + env['PRIMARY_HOSTNAME']]
|
secondary_ns = custom_secondary_ns or ["ns2." + env['PRIMARY_HOSTNAME']]
|
||||||
|
|
||||||
@@ -700,10 +700,11 @@ def query_dns(qname, rtype, nxdomain='[Not Set]', at=None):
|
|||||||
# BEGIN HOTFIX
|
# BEGIN HOTFIX
|
||||||
response_new = []
|
response_new = []
|
||||||
for r in response:
|
for r in response:
|
||||||
if isinstance(r.to_text(), bytes):
|
s = r.to_text()
|
||||||
response_new.append(r.to_text().decode('utf-8'))
|
if isinstance(s, bytes):
|
||||||
else:
|
s = s.decode('utf-8')
|
||||||
response_new.append(r)
|
response_new.append(s)
|
||||||
|
|
||||||
response = response_new
|
response = response_new
|
||||||
# END HOTFIX
|
# END HOTFIX
|
||||||
|
|
||||||
@@ -793,8 +794,13 @@ def what_version_is_this(env):
|
|||||||
def get_latest_miab_version():
|
def get_latest_miab_version():
|
||||||
# This pings https://mailinabox.email/setup.sh and extracts the tag named in
|
# This pings https://mailinabox.email/setup.sh and extracts the tag named in
|
||||||
# the script to determine the current product version.
|
# the script to determine the current product version.
|
||||||
import urllib.request
|
from urllib.request import urlopen, HTTPError, URLError
|
||||||
return re.search(b'TAG=(.*)', urllib.request.urlopen("https://mailinabox.email/setup.sh?ping=1").read()).group(1).decode("utf8")
|
from socket import timeout
|
||||||
|
|
||||||
|
try:
|
||||||
|
return re.search(b'TAG=(.*)', urlopen("https://mailinabox.email/setup.sh?ping=1", timeout=5).read()).group(1).decode("utf8")
|
||||||
|
except (HTTPError, URLError, timeout):
|
||||||
|
return None
|
||||||
|
|
||||||
def check_miab_version(env, output):
|
def check_miab_version(env, output):
|
||||||
config = load_settings(env)
|
config = load_settings(env)
|
||||||
@@ -811,6 +817,8 @@ def check_miab_version(env, output):
|
|||||||
|
|
||||||
if this_ver == latest_ver:
|
if this_ver == latest_ver:
|
||||||
output.print_ok("Mail-in-a-Box is up to date. You are running version %s." % this_ver)
|
output.print_ok("Mail-in-a-Box is up to date. You are running version %s." % this_ver)
|
||||||
|
elif latest_ver is None:
|
||||||
|
output.print_error("Latest Mail-in-a-Box version could not be determined. You are running version %s." % this_ver)
|
||||||
else:
|
else:
|
||||||
output.print_error("A new version of Mail-in-a-Box is available. You are running version %s. The latest version is %s. For upgrade instructions, see https://mailinabox.email. "
|
output.print_error("A new version of Mail-in-a-Box is available. You are running version %s. The latest version is %s. For upgrade instructions, see https://mailinabox.email. "
|
||||||
% (this_ver, latest_ver))
|
% (this_ver, latest_ver))
|
||||||
@@ -883,6 +891,14 @@ def run_and_output_changes(env, pool):
|
|||||||
with open(cache_fn, "w") as f:
|
with open(cache_fn, "w") as f:
|
||||||
json.dump(cur.buf, f, indent=True)
|
json.dump(cur.buf, f, indent=True)
|
||||||
|
|
||||||
|
def normalize_ip(ip):
|
||||||
|
# Use ipaddress module to normalize the IPv6 notation and ensure we are matching IPv6 addresses written in different representations according to rfc5952.
|
||||||
|
import ipaddress
|
||||||
|
try:
|
||||||
|
return str(ipaddress.ip_address(ip))
|
||||||
|
except:
|
||||||
|
return ip
|
||||||
|
|
||||||
class FileOutput:
|
class FileOutput:
|
||||||
def __init__(self, buf, width):
|
def __init__(self, buf, width):
|
||||||
self.buf = buf
|
self.buf = buf
|
||||||
|
|||||||
@@ -33,10 +33,12 @@
|
|||||||
<select id="customdnsType" class="form-control" style="max-width: 400px" onchange="show_customdns_rtype_hint()">
|
<select id="customdnsType" class="form-control" style="max-width: 400px" onchange="show_customdns_rtype_hint()">
|
||||||
<option value="A" data-hint="Enter an IPv4 address (i.e. a dotted quad, such as 123.456.789.012).">A (IPv4 address)</option>
|
<option value="A" data-hint="Enter an IPv4 address (i.e. a dotted quad, such as 123.456.789.012).">A (IPv4 address)</option>
|
||||||
<option value="AAAA" data-hint="Enter an IPv6 address.">AAAA (IPv6 address)</option>
|
<option value="AAAA" data-hint="Enter an IPv6 address.">AAAA (IPv6 address)</option>
|
||||||
|
<option value="CAA" data-hint="Enter a CA that can issue certificates for this domain in the form of FLAG TAG VALUE. (0 issuewild "letsencrypt.org")">CAA (Certificate Authority Authorization)</option>
|
||||||
<option value="CNAME" data-hint="Enter another domain name followed by a period at the end (e.g. mypage.github.io.).">CNAME (DNS forwarding)</option>
|
<option value="CNAME" data-hint="Enter another domain name followed by a period at the end (e.g. mypage.github.io.).">CNAME (DNS forwarding)</option>
|
||||||
<option value="TXT" data-hint="Enter arbitrary text.">TXT (text record)</option>
|
<option value="TXT" data-hint="Enter arbitrary text.">TXT (text record)</option>
|
||||||
<option value="MX" data-hint="Enter record in the form of PRIORITY DOMAIN., including trailing period (e.g. 20 mx.example.com.).">MX (mail exchanger)</option>
|
<option value="MX" data-hint="Enter record in the form of PRIORITY DOMAIN., including trailing period (e.g. 20 mx.example.com.).">MX (mail exchanger)</option>
|
||||||
<option value="SRV" data-hint="Enter record in the form of PRIORITY WEIGHT PORT TARGET., including trailing period (e.g. 10 10 5060 sip.example.com.).">SRV (service record)</option>
|
<option value="SRV" data-hint="Enter record in the form of PRIORITY WEIGHT PORT TARGET., including trailing period (e.g. 10 10 5060 sip.example.com.).">SRV (service record)</option>
|
||||||
|
<option value="SSHFP" data-hint="Enter record in the form of ALGORITHM TYPE FINGERPRINT.">SSHFP (SSH fingerprint record)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -124,7 +126,7 @@
|
|||||||
<tr><td>email</td> <td>The email address of any administrative user here.</td></tr>
|
<tr><td>email</td> <td>The email address of any administrative user here.</td></tr>
|
||||||
<tr><td>password</td> <td>That user’s password.</td></tr>
|
<tr><td>password</td> <td>That user’s password.</td></tr>
|
||||||
<tr><td>qname</td> <td>The fully qualified domain name for the record you are trying to set. It must be one of the domain names or a subdomain of one of the domain names hosted on this box. (Add mail users or aliases to add new domains.)</td></tr>
|
<tr><td>qname</td> <td>The fully qualified domain name for the record you are trying to set. It must be one of the domain names or a subdomain of one of the domain names hosted on this box. (Add mail users or aliases to add new domains.)</td></tr>
|
||||||
<tr><td>rtype</td> <td>The resource type. Defaults to <code>A</code> if omitted. Possible values: <code>A</code> (an IPv4 address), <code>AAAA</code> (an IPv6 address), <code>TXT</code> (a text string), <code>CNAME</code> (an alias, which is a fully qualified domain name — don’t forget the final period), <code>MX</code>, or <code>SRV</code>.</td></tr>
|
<tr><td>rtype</td> <td>The resource type. Defaults to <code>A</code> if omitted. Possible values: <code>A</code> (an IPv4 address), <code>AAAA</code> (an IPv6 address), <code>TXT</code> (a text string), <code>CNAME</code> (an alias, which is a fully qualified domain name — don’t forget the final period), <code>MX</code>, <code>SRV</code>, <code>SSHFP</code> or <code>CAA</code>.</td></tr>
|
||||||
<tr><td>value</td> <td>For PUT, POST, and DELETE, the record’s value. If the <code>rtype</code> is <code>A</code> or <code>AAAA</code> and <code>value</code> is empty or omitted, the IPv4 or IPv6 address of the remote host is used (be sure to use the <code>-4</code> or <code>-6</code> options to curl). This is handy for dynamic DNS!</td></tr>
|
<tr><td>value</td> <td>For PUT, POST, and DELETE, the record’s value. If the <code>rtype</code> is <code>A</code> or <code>AAAA</code> and <code>value</code> is empty or omitted, the IPv4 or IPv6 address of the remote host is used (be sure to use the <code>-4</code> or <code>-6</code> options to curl). This is handy for dynamic DNS!</td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
<link rel="stylesheet" href="/admin/assets/bootstrap.min.css">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
|
<link rel="stylesheet" href="/admin/assets/bootstrap-theme.min.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -191,8 +191,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js" integrity="sha256-rsPUGdUPBXgalvIj4YKJrrUlmLXbOb6Cp7cdxn1qeUc=" crossorigin="anonymous"></script>
|
<script src="/admin/assets/jquery.min.js"></script>
|
||||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
<script src="/admin/assets/bootstrap.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var global_modal_state = null;
|
var global_modal_state = null;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
<h4>Exchange/ActiveSync settings</h4>
|
<h4>Exchange/ActiveSync settings</h4>
|
||||||
|
|
||||||
<p>On iOS devices, devices on this <a href="http://z-push.org/compatibility/">compatibility list</a>, or using Outlook 2007 or later on Windows 7 and later, you may set up your mail as an Exchange or ActiveSync server. However, we’ve found this to be more buggy than using IMAP as described above. If you encounter any problems, please use the manual settings above.</p>
|
<p>On iOS devices, devices on this <a href="https://wiki.z-hub.io/display/ZP/Compatibility">compatibility list</a>, or using Outlook 2007 or later on Windows 7 and later, you may set up your mail as an Exchange or ActiveSync server. However, we’ve found this to be more buggy than using IMAP as described above. If you encounter any problems, please use the manual settings above.</p>
|
||||||
|
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tr><th>Server</th> <td>{{hostname}}</td></tr>
|
<tr><th>Server</th> <td>{{hostname}}</td></tr>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
<button type="submit" class="btn btn-primary">Add User</button>
|
<button type="submit" class="btn btn-primary">Add User</button>
|
||||||
</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 four characters and may not contain spaces. For best results, <a href="#" onclick="return generate_random_password()">generate a random password</a>.</li>
|
<li>Passwords must be at least eight characters and may not contain spaces. 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="#" onclick="return show_panel('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="#" onclick="return show_panel('aliases');">aliases</a> can.</li>
|
||||||
@@ -296,7 +296,7 @@ function mod_priv(elem, add_remove) {
|
|||||||
function generate_random_password() {
|
function generate_random_password() {
|
||||||
var pw = "";
|
var pw = "";
|
||||||
var charset = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; // confusable characters skipped
|
var charset = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; // confusable characters skipped
|
||||||
for (var i = 0; i < 10; i++)
|
for (var i = 0; i < 12; i++)
|
||||||
pw += charset.charAt(Math.floor(Math.random() * charset.length));
|
pw += charset.charAt(Math.floor(Math.random() * charset.length));
|
||||||
show_modal_error("Random Password", "<p>Here, try this:</p> <p><code style='font-size: 110%'>" + pw + "</code></pr");
|
show_modal_error("Random Password", "<p>Here, try this:</p> <p><code style='font-size: 110%'>" + pw + "</code></pr");
|
||||||
return false; // cancel click
|
return false; // cancel click
|
||||||
|
|||||||
@@ -106,76 +106,6 @@ def sort_email_addresses(email_addresses, env):
|
|||||||
ret.extend(sorted(email_addresses)) # whatever is left
|
ret.extend(sorted(email_addresses)) # whatever is left
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def exclusive_process(name):
|
|
||||||
# Ensure that a process named `name` does not execute multiple
|
|
||||||
# times concurrently.
|
|
||||||
import os, sys, atexit
|
|
||||||
pidfile = '/var/run/mailinabox-%s.pid' % name
|
|
||||||
mypid = os.getpid()
|
|
||||||
|
|
||||||
# Attempt to get a lock on ourself so that the concurrency check
|
|
||||||
# itself is not executed in parallel.
|
|
||||||
with open(__file__, 'r+') as flock:
|
|
||||||
# Try to get a lock. This blocks until a lock is acquired. The
|
|
||||||
# lock is held until the flock file is closed at the end of the
|
|
||||||
# with block.
|
|
||||||
os.lockf(flock.fileno(), os.F_LOCK, 0)
|
|
||||||
|
|
||||||
# While we have a lock, look at the pid file. First attempt
|
|
||||||
# to write our pid to a pidfile if no file already exists there.
|
|
||||||
try:
|
|
||||||
with open(pidfile, 'x') as f:
|
|
||||||
# Successfully opened a new file. Since the file is new
|
|
||||||
# there is no concurrent process. Write our pid.
|
|
||||||
f.write(str(mypid))
|
|
||||||
atexit.register(clear_my_pid, pidfile)
|
|
||||||
return
|
|
||||||
except FileExistsError:
|
|
||||||
# The pid file already exixts, but it may contain a stale
|
|
||||||
# pid of a terminated process.
|
|
||||||
with open(pidfile, 'r+') as f:
|
|
||||||
# Read the pid in the file.
|
|
||||||
existing_pid = None
|
|
||||||
try:
|
|
||||||
existing_pid = int(f.read().strip())
|
|
||||||
except ValueError:
|
|
||||||
pass # No valid integer in the file.
|
|
||||||
|
|
||||||
# Check if the pid in it is valid.
|
|
||||||
if existing_pid:
|
|
||||||
if is_pid_valid(existing_pid):
|
|
||||||
print("Another %s is already running (pid %d)." % (name, existing_pid), file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Write our pid.
|
|
||||||
f.seek(0)
|
|
||||||
f.write(str(mypid))
|
|
||||||
f.truncate()
|
|
||||||
atexit.register(clear_my_pid, pidfile)
|
|
||||||
|
|
||||||
|
|
||||||
def clear_my_pid(pidfile):
|
|
||||||
import os
|
|
||||||
os.unlink(pidfile)
|
|
||||||
|
|
||||||
|
|
||||||
def is_pid_valid(pid):
|
|
||||||
"""Checks whether a pid is a valid process ID of a currently running process."""
|
|
||||||
# adapted from http://stackoverflow.com/questions/568271/how-to-check-if-there-exists-a-process-with-a-given-pid
|
|
||||||
import os, errno
|
|
||||||
if pid <= 0: raise ValueError('Invalid PID.')
|
|
||||||
try:
|
|
||||||
os.kill(pid, 0)
|
|
||||||
except OSError as err:
|
|
||||||
if err.errno == errno.ESRCH: # No such process
|
|
||||||
return False
|
|
||||||
elif err.errno == errno.EPERM: # Not permitted to send signal
|
|
||||||
return True
|
|
||||||
else: # EINVAL
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def shell(method, cmd_args, env={}, capture_stderr=False, return_bytes=False, trap=False, input=None):
|
def shell(method, cmd_args, env={}, capture_stderr=False, return_bytes=False, trap=False, input=None):
|
||||||
# A safe way to execute processes.
|
# A safe way to execute processes.
|
||||||
# Some processes like apt-get require being given a sane PATH.
|
# Some processes like apt-get require being given a sane PATH.
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ If DNSSEC is enabled at the box's domain name's registrar, the SSHFP record that
|
|||||||
|
|
||||||
`fail2ban` provides some protection from brute-force login attacks (repeated logins that guess account passwords) by blocking offending IP addresses at the network level.
|
`fail2ban` provides some protection from brute-force login attacks (repeated logins that guess account passwords) by blocking offending IP addresses at the network level.
|
||||||
|
|
||||||
The following services are protected: SSH, IMAP (dovecot), SMTP submission (postfix), webmail (roundcube), ownCloud/CalDAV/CardDAV (over HTTP), and the Mail-in-a-Box control panel & munin (over HTTP).
|
The following services are protected: SSH, IMAP (dovecot), SMTP submission (postfix), webmail (roundcube), Nextcloud/CalDAV/CardDAV (over HTTP), and the Mail-in-a-Box control panel & munin (over HTTP).
|
||||||
|
|
||||||
Some other services running on the box may be missing fail2ban filters.
|
Some other services running on the box may be missing fail2ban filters.
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#########################################################
|
#########################################################
|
||||||
|
|
||||||
if [ -z "$TAG" ]; then
|
if [ -z "$TAG" ]; then
|
||||||
TAG=v0.21
|
TAG=v0.23
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Are we running as root?
|
# Are we running as root?
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ apt_install nsd ldnsutils openssh-client
|
|||||||
mkdir -p /var/run/nsd
|
mkdir -p /var/run/nsd
|
||||||
|
|
||||||
cat > /etc/nsd/nsd.conf << EOF;
|
cat > /etc/nsd/nsd.conf << EOF;
|
||||||
# No not edit. Overwritten by Mail-in-a-Box setup.
|
# Do not edit. Overwritten by Mail-in-a-Box setup.
|
||||||
server:
|
server:
|
||||||
hide-version: yes
|
hide-version: yes
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ if [ -z "`tools/mail.py user`" ]; then
|
|||||||
else
|
else
|
||||||
# Use me@PRIMARY_HOSTNAME
|
# Use me@PRIMARY_HOSTNAME
|
||||||
EMAIL_ADDR=me@$PRIMARY_HOSTNAME
|
EMAIL_ADDR=me@$PRIMARY_HOSTNAME
|
||||||
EMAIL_PW=1234
|
EMAIL_PW=12345678
|
||||||
echo
|
echo
|
||||||
echo "Creating a new administrative mail account for $EMAIL_ADDR with password $EMAIL_PW."
|
echo "Creating a new administrative mail account for $EMAIL_ADDR with password $EMAIL_PW."
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -37,8 +37,16 @@ apt_install \
|
|||||||
# of active IMAP connections (at, say, 5 open connections per user that
|
# of active IMAP connections (at, say, 5 open connections per user that
|
||||||
# would be 20 users). Set it to 250 times the number of cores this
|
# would be 20 users). Set it to 250 times the number of cores this
|
||||||
# machine has, so on a two-core machine that's 500 processes/100 users).
|
# machine has, so on a two-core machine that's 500 processes/100 users).
|
||||||
|
# The `default_vsz_limit` is the maximum amount of virtual memory that
|
||||||
|
# can be allocated. It should be set *reasonably high* to avoid allocation
|
||||||
|
# issues with larger mailboxes. We're setting it to 1/3 of the total
|
||||||
|
# available memory (physical mem + swap) to be sure.
|
||||||
|
# See here for discussion:
|
||||||
|
# - https://www.dovecot.org/list/dovecot/2012-August/137569.html
|
||||||
|
# - https://www.dovecot.org/list/dovecot/2011-December/132455.html
|
||||||
tools/editconf.py /etc/dovecot/conf.d/10-master.conf \
|
tools/editconf.py /etc/dovecot/conf.d/10-master.conf \
|
||||||
default_process_limit=$(echo "`nproc` * 250" | bc) \
|
default_process_limit=$(echo "`nproc` * 250" | bc) \
|
||||||
|
default_vsz_limit=$(echo "`free -tom | tail -1 | awk '{print $2}'` / 3" | bc)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
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ source setup/functions.sh
|
|||||||
|
|
||||||
echo "Installing Mail-in-a-Box system management daemon..."
|
echo "Installing Mail-in-a-Box system management daemon..."
|
||||||
|
|
||||||
# Install packages.
|
# DEPENDENCIES
|
||||||
|
|
||||||
|
# Install Python packages that are available from the Ubuntu
|
||||||
|
# apt repository:
|
||||||
# flask, yaml, dnspython, and dateutil are all for our Python 3 management daemon itself.
|
# flask, yaml, dnspython, and dateutil are all for our Python 3 management daemon itself.
|
||||||
# duplicity does backups. python-pip is so we can 'pip install boto' for Python 2, for duplicity, so it can do backups to AWS S3.
|
# duplicity does backups. python-pip is so we can 'pip install boto' for Python 2, for duplicity, so it can do backups to AWS S3.
|
||||||
apt_install python3-flask links duplicity libyaml-dev python3-dnspython python3-dateutil python-pip
|
apt_install python3-flask links duplicity libyaml-dev python3-dnspython python3-dateutil python-pip
|
||||||
@@ -12,17 +15,45 @@ apt_install python3-flask links duplicity libyaml-dev python3-dnspython python3-
|
|||||||
# These are required to pip install cryptography.
|
# These are required to pip install cryptography.
|
||||||
apt_install build-essential libssl-dev libffi-dev python3-dev
|
apt_install build-essential libssl-dev libffi-dev python3-dev
|
||||||
|
|
||||||
|
# pip<6.1 + setuptools>=34 have a problem with packages that
|
||||||
|
# try to update setuptools during installation, like cryptography.
|
||||||
|
# See https://github.com/pypa/pip/issues/4253. The Ubuntu 14.04
|
||||||
|
# package versions are pip 1.5.4 and setuptools 3.3. When we
|
||||||
|
# install cryptography under those versions, it tries to update
|
||||||
|
# setuptools to version 34, which now creates the conflict, and
|
||||||
|
# then pip gets permanently broken with errors like
|
||||||
|
# "ImportError: No module named 'packaging'".
|
||||||
|
#
|
||||||
|
# Let's test for the error:
|
||||||
|
if ! python3 -c "from pkg_resources import load_entry_point" 2&> /dev/null; then
|
||||||
|
# This system seems to be broken already.
|
||||||
|
echo "Fixing broken pip and setuptools..."
|
||||||
|
rm -rf /usr/local/lib/python3.4/dist-packages/{pkg_resources,setuptools}*
|
||||||
|
apt-get install --reinstall python3-setuptools python3-pip python3-pkg-resources
|
||||||
|
fi
|
||||||
|
#
|
||||||
|
# The easiest work-around on systems that aren't already broken is
|
||||||
|
# to upgrade pip (to >=9.0.1) and setuptools (to >=34.1) individually
|
||||||
|
# before we install any package that tries to update setuptools.
|
||||||
|
hide_output pip3 install --upgrade pip
|
||||||
|
hide_output pip3 install --upgrade setuptools
|
||||||
|
|
||||||
# Install other Python 3 packages used by the management daemon.
|
# Install other Python 3 packages used by the management daemon.
|
||||||
# The first line is the packages that Josh maintains himself!
|
# The first line is the packages that Josh maintains himself!
|
||||||
# NOTE: email_validator is repeated in setup/questions.sh, so please keep the versions synced.
|
# NOTE: email_validator is repeated in setup/questions.sh, so please keep the versions synced.
|
||||||
|
# Force acme to be updated because it seems to need it after the
|
||||||
|
# pip/setuptools breakage (see above) and the ACME protocol may
|
||||||
|
# have changed (I got an error on one of my systems).
|
||||||
hide_output pip3 install --upgrade \
|
hide_output pip3 install --upgrade \
|
||||||
rtyaml "email_validator>=1.0.0" "free_tls_certificates>=0.1.3" \
|
rtyaml "email_validator>=1.0.0" "free_tls_certificates>=0.1.3" "exclusiveprocess" \
|
||||||
"idna>=2.0.0" "cryptography>=1.0.2" boto psutil
|
"idna>=2.0.0" "cryptography>=1.0.2" acme boto psutil
|
||||||
|
|
||||||
# duplicity uses python 2 so we need to get the python 2 package of boto to have backups to S3.
|
# duplicity uses python 2 so we need to get the python 2 package of boto to have backups to S3.
|
||||||
# boto from the Ubuntu package manager is too out-of-date -- it doesn't support the newer
|
# boto from the Ubuntu package manager is too out-of-date -- it doesn't support the newer
|
||||||
# S3 api used in some regions, which breaks backups to those regions. See #627, #653.
|
# S3 api used in some regions, which breaks backups to those regions. See #627, #653.
|
||||||
hide_output pip install --upgrade boto
|
hide_output pip2 install --upgrade boto
|
||||||
|
|
||||||
|
# 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
|
||||||
@@ -30,6 +61,32 @@ 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
|
||||||
|
|
||||||
|
|
||||||
|
# Download jQuery and Bootstrap local files
|
||||||
|
|
||||||
|
# Make sure we have the directory to save to.
|
||||||
|
assets_dir=/usr/local/lib/mailinabox/vendor/assets
|
||||||
|
mkdir -p $assets_dir
|
||||||
|
|
||||||
|
# jQuery CDN URL
|
||||||
|
jquery_version=2.1.4
|
||||||
|
jquery_url=https://code.jquery.com
|
||||||
|
|
||||||
|
# Get jQuery
|
||||||
|
wget_verify $jquery_url/jquery-$jquery_version.min.js 43dc554608df885a59ddeece1598c6ace434d747 $assets_dir/jquery.min.js
|
||||||
|
|
||||||
|
# Bootstrap CDN URL
|
||||||
|
bootstrap_version=3.3.7
|
||||||
|
bootstrap_url=https://maxcdn.bootstrapcdn.com/bootstrap/$bootstrap_version
|
||||||
|
|
||||||
|
# Get Bootstrap
|
||||||
|
wget_verify $bootstrap_url/js/bootstrap.min.js 430a443d74830fe9be26efca431f448c1b3740f9 $assets_dir/bootstrap.min.js
|
||||||
|
wget_verify $bootstrap_url/css/bootstrap-theme.min.css 8256575374f430476bdcd49de98c77990229ce31 $assets_dir/bootstrap-theme.min.css
|
||||||
|
wget_verify $bootstrap_url/css/bootstrap-theme.min.css.map 87f7dfd79d77051ac2eca7d093d961fbd1c8f6eb $assets_dir/bootstrap-theme.min.css.map
|
||||||
|
wget_verify $bootstrap_url/css/bootstrap.min.css 6527d8bf3e1e9368bab8c7b60f56bc01fa3afd68 $assets_dir/bootstrap.min.css
|
||||||
|
wget_verify $bootstrap_url/css/bootstrap.min.css.map e0d7b2bde55a0bac1b658a507e8ca491a6729e06 $assets_dir/bootstrap.min.css.map
|
||||||
|
|
||||||
|
|
||||||
# Link the management server daemon into a well known location.
|
# Link the management server daemon into a well known location.
|
||||||
rm -f /usr/local/bin/mailinabox-daemon
|
rm -f /usr/local/bin/mailinabox-daemon
|
||||||
ln -s `pwd`/management/daemon.py /usr/local/bin/mailinabox-daemon
|
ln -s `pwd`/management/daemon.py /usr/local/bin/mailinabox-daemon
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Owncloud
|
# Nextcloud
|
||||||
##########################
|
##########################
|
||||||
|
|
||||||
source setup/functions.sh # load our functions
|
source setup/functions.sh # load our functions
|
||||||
source /etc/mailinabox.conf # load global vars
|
source /etc/mailinabox.conf # load global vars
|
||||||
|
|
||||||
# ### Installing ownCloud
|
# ### Installing Nextcloud
|
||||||
|
|
||||||
echo "Installing ownCloud (contacts/calendar)..."
|
echo "Installing Nextcloud (contacts/calendar)..."
|
||||||
|
|
||||||
apt_install \
|
apt_install \
|
||||||
dbconfig-common \
|
dbconfig-common \
|
||||||
php5-cli php5-sqlite php5-gd php5-imap php5-curl php-pear php-apc curl libapr1 libtool libcurl4-openssl-dev php-xml-parser \
|
php5-cli php5-sqlite php5-gd php5-imap php5-curl php-pear php-apc curl libapr1 libtool libcurl4-openssl-dev php-xml-parser \
|
||||||
php5 php5-dev php5-gd php5-fpm memcached php5-memcached unzip
|
php5 php5-dev php5-gd php5-fpm memcached php5-memcached
|
||||||
|
|
||||||
apt-get purge -qq -y owncloud*
|
apt-get purge -qq -y owncloud*
|
||||||
|
|
||||||
@@ -29,31 +29,51 @@ if [ ! -f $STORAGE_ROOT/owncloud/config.php ] \
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
InstallOwncloud() {
|
InstallOwncloud() {
|
||||||
|
|
||||||
|
version=$1
|
||||||
|
hash=$2
|
||||||
|
flavor=$3
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Upgrading to ownCloud version $1"
|
echo "Upgrading to $flavor version $version"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
version=$1
|
# Remove the current owncloud/Nextcloud
|
||||||
hash=$2
|
|
||||||
|
|
||||||
# Remove the current owncloud
|
|
||||||
rm -rf /usr/local/lib/owncloud
|
rm -rf /usr/local/lib/owncloud
|
||||||
|
|
||||||
# Download and verify
|
# Download and verify
|
||||||
wget_verify https://download.owncloud.org/community/owncloud-$version.zip $hash /tmp/owncloud.zip
|
if [ "$flavor" = "Nextcloud" ]; then
|
||||||
|
wget_verify https://download.nextcloud.com/server/releases/nextcloud-$version.zip $hash /tmp/owncloud.zip
|
||||||
|
else
|
||||||
|
wget_verify https://download.owncloud.org/community/owncloud-$version.zip $hash /tmp/owncloud.zip
|
||||||
|
fi
|
||||||
|
|
||||||
# Extract ownCloud
|
# Extract ownCloud/Nextcloud
|
||||||
unzip -q /tmp/owncloud.zip -d /usr/local/lib
|
unzip -q /tmp/owncloud.zip -d /usr/local/lib
|
||||||
|
if [ "$flavor" = "Nextcloud" ]; then
|
||||||
|
mv /usr/local/lib/nextcloud /usr/local/lib/owncloud
|
||||||
|
fi
|
||||||
rm -f /tmp/owncloud.zip
|
rm -f /tmp/owncloud.zip
|
||||||
|
|
||||||
# The two apps we actually want are not in ownCloud core. Download the releases from
|
# The two apps we actually want are not in Nextcloud core. Download the releases from
|
||||||
# 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/owncloud/contacts/releases/download/v1.4.0.0/contacts.tar.gz c1c22d29699456a45db447281682e8bc3f10e3e7 /tmp/contacts.tgz
|
|
||||||
|
if [ "$flavor" = "Nextcloud" ]; then
|
||||||
|
wget_verify https://github.com/nextcloud/contacts/releases/download/v1.5.3/contacts.tar.gz 78c4d49e73f335084feecd4853bd8234cf32615e /tmp/contacts.tgz
|
||||||
|
else
|
||||||
|
wget_verify https://github.com/owncloud/contacts/releases/download/v1.4.0.0/contacts.tar.gz c1c22d29699456a45db447281682e8bc3f10e3e7 /tmp/contacts.tgz
|
||||||
|
fi
|
||||||
|
|
||||||
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/calendar/releases/download/v1.4.0/calendar.tar.gz c84f3170efca2a99ea6254de34b0af3cb0b3a821 /tmp/calendar.tgz
|
if [ "$flavor" = "Nextcloud" ]; then
|
||||||
|
wget_verify https://github.com/nextcloud/calendar/releases/download/v1.5.2/calendar.tar.gz 7b8a94e01fe740c5c23017ed5bc211983c780fce /tmp/calendar.tgz
|
||||||
|
else
|
||||||
|
wget_verify https://github.com/nextcloud/calendar/releases/download/v1.4.0/calendar.tar.gz c84f3170efca2a99ea6254de34b0af3cb0b3a821 /tmp/calendar.tgz
|
||||||
|
fi
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -85,21 +105,23 @@ InstallOwncloud() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
owncloud_ver=9.1.1
|
owncloud_ver=10.0.5
|
||||||
|
owncloud_hash=686f6a8e9d7867c32e3bf3ca63b3cc2020564bf6
|
||||||
|
owncloud_flavor=Nextcloud
|
||||||
|
|
||||||
# Check if ownCloud dir exist, and check if version matches owncloud_ver (if either doesn't - install/upgrade)
|
# Check if Nextcloud dir exist, and check if version matches owncloud_ver (if either doesn't - install/upgrade)
|
||||||
if [ ! -d /usr/local/lib/owncloud/ ] \
|
if [ ! -d /usr/local/lib/owncloud/ ] \
|
||||||
|| ! grep -q $owncloud_ver /usr/local/lib/owncloud/version.php; then
|
|| ! grep -q $owncloud_ver /usr/local/lib/owncloud/version.php; then
|
||||||
|
|
||||||
# Stop php-fpm
|
# Stop php-fpm
|
||||||
hide_output service php5-fpm stop
|
hide_output service php5-fpm stop
|
||||||
|
|
||||||
# Backup the existing ownCloud.
|
# 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
|
||||||
BACKUP_DIRECTORY=$STORAGE_ROOT/owncloud-backup/`date +"%Y-%m-%d-%T"`
|
BACKUP_DIRECTORY=$STORAGE_ROOT/owncloud-backup/`date +"%Y-%m-%d-%T"`
|
||||||
mkdir -p "$BACKUP_DIRECTORY"
|
mkdir -p "$BACKUP_DIRECTORY"
|
||||||
if [ -d /usr/local/lib/owncloud/ ]; then
|
if [ -d /usr/local/lib/owncloud/ ]; then
|
||||||
echo "upgrading ownCloud to $owncloud_ver (backing up existing ownCloud installation, configuration and database to directory to $BACKUP_DIRECTORY..."
|
echo "upgrading ownCloud/Nextcloud to $owncloud_flavor $owncloud_ver (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 /home/user-data/owncloud/owncloud.db ]; then
|
if [ -e /home/user-data/owncloud/owncloud.db ]; then
|
||||||
@@ -109,15 +131,15 @@ if [ ! -d /usr/local/lib/owncloud/ ] \
|
|||||||
cp /home/user-data/owncloud/config.php $BACKUP_DIRECTORY
|
cp /home/user-data/owncloud/config.php $BACKUP_DIRECTORY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# We only need to check if we do upgrades when owncloud was previously installed
|
# We only need to check if we do upgrades when owncloud/Nextcloud was previously installed
|
||||||
if [ -e /usr/local/lib/owncloud/version.php ]; then
|
if [ -e /usr/local/lib/owncloud/version.php ]; then
|
||||||
if grep -q "8.1.[0-9]" /usr/local/lib/owncloud/version.php; then
|
if grep -q "8\.1\.[0-9]" /usr/local/lib/owncloud/version.php; then
|
||||||
echo "We are running 8.1.x, upgrading to 8.2.3 first"
|
echo "We are running 8.1.x, upgrading to 8.2.3 first"
|
||||||
InstallOwncloud 8.2.3 bfdf6166fbf6fc5438dc358600e7239d1c970613
|
InstallOwncloud 8.2.3 bfdf6166fbf6fc5438dc358600e7239d1c970613 ownCloud
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# If we are upgrading from 8.2.x we should go to 9.0 first. Owncloud doesn't support skipping minor versions
|
# If we are upgrading from 8.2.x we should go to 9.0 first. Owncloud doesn't support skipping minor versions
|
||||||
if grep -q "8.2.[0-9]" /usr/local/lib/owncloud/version.php; then
|
if grep -q "8\.2\.[0-9]" /usr/local/lib/owncloud/version.php; then
|
||||||
echo "We are running version 8.2.x, upgrading to 9.0.2 first"
|
echo "We are running version 8.2.x, upgrading to 9.0.2 first"
|
||||||
|
|
||||||
# We need to disable memcached. The upgrade and install fails
|
# We need to disable memcached. The upgrade and install fails
|
||||||
@@ -137,7 +159,7 @@ EOF
|
|||||||
chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
|
chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
|
||||||
|
|
||||||
# We can now install owncloud 9.0.2
|
# We can now install owncloud 9.0.2
|
||||||
InstallOwncloud 9.0.2 72a3d15d09f58c06fa8bee48b9e60c9cd356f9c5
|
InstallOwncloud 9.0.2 72a3d15d09f58c06fa8bee48b9e60c9cd356f9c5 ownCloud
|
||||||
|
|
||||||
# The owncloud 9 migration doesn't migrate calendars and contacts
|
# The owncloud 9 migration doesn't migrate calendars and contacts
|
||||||
# The option to migrate these are removed in 9.1
|
# The option to migrate these are removed in 9.1
|
||||||
@@ -150,14 +172,21 @@ EOF
|
|||||||
done
|
done
|
||||||
sudo -u www-data php /usr/local/lib/owncloud/occ dav:sync-birthday-calendar
|
sudo -u www-data php /usr/local/lib/owncloud/occ dav:sync-birthday-calendar
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# If we are upgrading from 9.0.x we should go to 9.1 first.
|
||||||
|
if grep -q "9\.0\.[0-9]" /usr/local/lib/owncloud/version.php; then
|
||||||
|
echo "We are running ownCloud 9.0.x, upgrading to ownCloud 9.1.4 first"
|
||||||
|
InstallOwncloud 9.1.4 e637cab7b2ca3346164f3506b1a0eb812b4e841a ownCloud
|
||||||
|
fi
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
InstallOwncloud $owncloud_ver 72ed9812432f01b3a459c4afc33f5c76b71eec09
|
InstallOwncloud $owncloud_ver $owncloud_hash Nextcloud
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ### Configuring ownCloud
|
# ### Configuring Nextcloud
|
||||||
|
|
||||||
# Setup ownCloud if the ownCloud 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
|
||||||
@@ -172,7 +201,7 @@ if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
|
|||||||
|
|
||||||
'instanceid' => '$instanceid',
|
'instanceid' => '$instanceid',
|
||||||
|
|
||||||
'forcessl' => true, # if unset/false, ownCloud sends a HSTS=0 header, which conflicts with nginx config
|
'forcessl' => true, # if unset/false, Nextcloud sends a HSTS=0 header, which conflicts with nginx config
|
||||||
|
|
||||||
'overwritewebroot' => '/cloud',
|
'overwritewebroot' => '/cloud',
|
||||||
'overwrite.cli.url' => '/cloud',
|
'overwrite.cli.url' => '/cloud',
|
||||||
@@ -192,7 +221,6 @@ if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
|
|||||||
'mail_smtpname' => '',
|
'mail_smtpname' => '',
|
||||||
'mail_smtppassword' => '',
|
'mail_smtppassword' => '',
|
||||||
'mail_from_address' => 'owncloud',
|
'mail_from_address' => 'owncloud',
|
||||||
'mail_domain' => '$PRIMARY_HOSTNAME',
|
|
||||||
);
|
);
|
||||||
?>
|
?>
|
||||||
EOF
|
EOF
|
||||||
@@ -209,7 +237,7 @@ EOF
|
|||||||
'dbtype' => 'sqlite3',
|
'dbtype' => 'sqlite3',
|
||||||
|
|
||||||
# create an administrator account with a random password so that
|
# create an administrator account with a random password so that
|
||||||
# the user does not have to enter anything on first load of ownCloud
|
# the user does not have to enter anything on first load of Nextcloud
|
||||||
'adminlogin' => 'root',
|
'adminlogin' => 'root',
|
||||||
'adminpass' => '$adminpassword',
|
'adminpass' => '$adminpassword',
|
||||||
);
|
);
|
||||||
@@ -219,7 +247,7 @@ 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 ownCloud's setup step, which creates the ownCloud 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 /usr/local/lib/owncloud/index.php;)
|
(cd /usr/local/lib/owncloud; sudo -u www-data php /usr/local/lib/owncloud/index.php;)
|
||||||
@@ -233,6 +261,8 @@ fi
|
|||||||
# * We need to set the timezone to the system timezone to allow fail2ban to ban
|
# * We need to set the timezone to the system timezone to allow fail2ban to ban
|
||||||
# users within the proper timeframe
|
# users within the proper timeframe
|
||||||
# * We need to set the logdateformat to something that will work correctly with fail2ban
|
# * We need to set the logdateformat to something that will work correctly with fail2ban
|
||||||
|
# * mail_domain' needs to be set every time we run the setup. Making sure we are setting
|
||||||
|
# the correct domain name if the domain is being change from the previous setup.
|
||||||
# Use PHP to read the settings file, modify it, and write out the new settings array.
|
# 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)
|
||||||
@@ -249,6 +279,8 @@ include("$STORAGE_ROOT/owncloud/config.php");
|
|||||||
\$CONFIG['logtimezone'] = '$TIMEZONE';
|
\$CONFIG['logtimezone'] = '$TIMEZONE';
|
||||||
\$CONFIG['logdateformat'] = 'Y-m-d H:i:s';
|
\$CONFIG['logdateformat'] = 'Y-m-d H:i:s';
|
||||||
|
|
||||||
|
\$CONFIG['mail_domain'] = '$PRIMARY_HOSTNAME';
|
||||||
|
|
||||||
echo "<?php\n\\\$CONFIG = ";
|
echo "<?php\n\\\$CONFIG = ";
|
||||||
var_export(\$CONFIG);
|
var_export(\$CONFIG);
|
||||||
echo ";";
|
echo ";";
|
||||||
@@ -256,9 +288,9 @@ 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 ownCloud 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 ownCloud 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 /usr/local/lib/owncloud/console.php app:disable firstrunwizard
|
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:disable firstrunwizard
|
||||||
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:enable user_external
|
hide_output sudo -u www-data php /usr/local/lib/owncloud/console.php app:enable user_external
|
||||||
@@ -287,7 +319,7 @@ if grep -q apc.enabled=0 /etc/php5/mods-available/apcu.ini; then
|
|||||||
apc.enabled=1
|
apc.enabled=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set up a cron job for owncloud.
|
# Set up a cron job for Nextcloud.
|
||||||
cat > /etc/cron.hourly/mailinabox-owncloud << EOF;
|
cat > /etc/cron.hourly/mailinabox-owncloud << EOF;
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Mail-in-a-Box
|
# Mail-in-a-Box
|
||||||
@@ -295,8 +327,8 @@ sudo -u www-data php -f /usr/local/lib/owncloud/cron.php
|
|||||||
EOF
|
EOF
|
||||||
chmod +x /etc/cron.hourly/mailinabox-owncloud
|
chmod +x /etc/cron.hourly/mailinabox-owncloud
|
||||||
|
|
||||||
# There's nothing much of interest that a user could do as an admin for ownCloud,
|
# 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 ownCloud.
|
# 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:
|
||||||
# ```
|
# ```
|
||||||
# for user in $(tools/mail.py user admins); do
|
# for user in $(tools/mail.py user admins); do
|
||||||
|
|||||||
@@ -180,9 +180,6 @@ if [ "$PUBLIC_IPV6" = "auto" ]; then
|
|||||||
fi
|
fi
|
||||||
if [ "$PRIMARY_HOSTNAME" = "auto" ]; then
|
if [ "$PRIMARY_HOSTNAME" = "auto" ]; then
|
||||||
PRIMARY_HOSTNAME=$(get_default_hostname)
|
PRIMARY_HOSTNAME=$(get_default_hostname)
|
||||||
elif [ "$PRIMARY_HOSTNAME" = "auto-easy" ]; then
|
|
||||||
# Generate a probably-unique subdomain under our justtesting.email domain.
|
|
||||||
PRIMARY_HOSTNAME=`echo $PUBLIC_IP | sha1sum | cut -c1-5`.justtesting.email
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set STORAGE_USER and STORAGE_ROOT to default values (user-data and /home/user-data), unless
|
# Set STORAGE_USER and STORAGE_ROOT to default values (user-data and /home/user-data), unless
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ echo "public.pyzor.org:24441" > /etc/spamassassin/pyzor/servers
|
|||||||
# * Disable localmode so Pyzor, DKIM and DNS checks can be used.
|
# * Disable localmode so Pyzor, DKIM and DNS checks can be used.
|
||||||
tools/editconf.py /etc/default/spampd \
|
tools/editconf.py /etc/default/spampd \
|
||||||
DESTPORT=10026 \
|
DESTPORT=10026 \
|
||||||
ADDOPTS="\"--maxsize=500\"" \
|
ADDOPTS="\"--maxsize=2000\"" \
|
||||||
LOCALONLY=0
|
LOCALONLY=0
|
||||||
|
|
||||||
# Spamassassin normally wraps spam as an attachment inside a fresh
|
# Spamassassin normally wraps spam as an attachment inside a fresh
|
||||||
@@ -63,7 +63,8 @@ tools/editconf.py /etc/default/spampd \
|
|||||||
# Tell Spamassassin not to modify the original message except for adding
|
# Tell Spamassassin not to modify the original message except for adding
|
||||||
# the X-Spam-Status mail header and related headers.
|
# the X-Spam-Status mail header and related headers.
|
||||||
tools/editconf.py /etc/spamassassin/local.cf -s \
|
tools/editconf.py /etc/spamassassin/local.cf -s \
|
||||||
report_safe=0
|
report_safe=0 \
|
||||||
|
add_header="all Report _REPORT_"
|
||||||
|
|
||||||
# Bayesean learning
|
# Bayesean learning
|
||||||
# -----------------
|
# -----------------
|
||||||
|
|||||||
@@ -147,17 +147,17 @@ if management/status_checks.py --check-primary-hostname; then
|
|||||||
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 SSL fingerprint:"
|
echo "(https://$PUBLIC_IP/admin) but then check the TLS fingerprint:"
|
||||||
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint \
|
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint -sha256\
|
||||||
| sed "s/SHA1 Fingerprint=//"
|
| sed "s/SHA256 Fingerprint=//"
|
||||||
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 \
|
openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint -sha256\
|
||||||
| sed "s/SHA1 Fingerprint=//"
|
| sed "s/SHA256 Fingerprint=//"
|
||||||
echo
|
echo
|
||||||
echo Then you can confirm the security exception and continue.
|
echo Then you can confirm the security exception and continue.
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ apt_get_quiet upgrade
|
|||||||
echo Installing system packages...
|
echo Installing system packages...
|
||||||
apt_install python3 python3-dev python3-pip \
|
apt_install python3 python3-dev python3-pip \
|
||||||
netcat-openbsd wget curl git sudo coreutils bc \
|
netcat-openbsd wget curl git sudo coreutils bc \
|
||||||
haveged pollinate \
|
haveged pollinate unzip \
|
||||||
unattended-upgrades cron ntp fail2ban
|
unattended-upgrades cron ntp fail2ban
|
||||||
|
|
||||||
# ### Suppress Upgrade Prompts
|
# ### Suppress Upgrade Prompts
|
||||||
|
|||||||
@@ -34,12 +34,21 @@ apt-get purge -qq -y roundcube* #NODOC
|
|||||||
# 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 vacation_sieve to track
|
# Combine the Roundcube version number with the commit hash of vacation_sieve to track
|
||||||
# whether we have the latest version.
|
# whether we have the latest version.
|
||||||
VERSION=1.2.1
|
VERSION=1.2.4
|
||||||
HASH=81fbfba4683522f6e54006d0300a48e6da3f3bbd
|
HASH=e2091ea775b80eda43ab225130d5a2e888c3789a
|
||||||
VACATION_SIEVE_VERSION=91ea6f52216390073d1f5b70b5f6bea0bfaee7e5
|
VACATION_SIEVE_VERSION=91ea6f52216390073d1f5b70b5f6bea0bfaee7e5
|
||||||
PERSISTENT_LOGIN_VERSION=1e9d724476a370ce917a2fcd5b3217b0c306c24e
|
PERSISTENT_LOGIN_VERSION=c4516c4be37d12ef653de86497304e073a863c2a
|
||||||
HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5
|
HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5
|
||||||
UPDATE_KEY=$VERSION:$VACATION_SIEVE_VERSION:$PERSISTENT_LOGIN_VERSION:$HTML5_NOTIFIER_VERSION:a
|
CARDDAV_VERSION=2.0.4
|
||||||
|
CARDDAV_HASH=d93f3cfb3038a519e71c7c3212c1d16f5da609a4
|
||||||
|
|
||||||
|
UPDATE_KEY=$VERSION:$VACATION_SIEVE_VERSION:$PERSISTENT_LOGIN_VERSION:$HTML5_NOTIFIER_VERSION:$CARDDAV_VERSION:a
|
||||||
|
|
||||||
|
# paths that are often reused.
|
||||||
|
RCM_DIR=/usr/local/lib/roundcubemail
|
||||||
|
RCM_PLUGIN_DIR=${RCM_DIR}/plugins
|
||||||
|
RCM_CONFIG=${RCM_DIR}/config/config.inc.php
|
||||||
|
|
||||||
needs_update=0 #NODOC
|
needs_update=0 #NODOC
|
||||||
if [ ! -f /usr/local/lib/roundcubemail/version ]; then
|
if [ ! -f /usr/local/lib/roundcubemail/version ]; then
|
||||||
# not installed yet #NODOC
|
# not installed yet #NODOC
|
||||||
@@ -56,20 +65,30 @@ if [ $needs_update == 1 ]; then
|
|||||||
/tmp/roundcube.tgz
|
/tmp/roundcube.tgz
|
||||||
tar -C /usr/local/lib --no-same-owner -zxf /tmp/roundcube.tgz
|
tar -C /usr/local/lib --no-same-owner -zxf /tmp/roundcube.tgz
|
||||||
rm -rf /usr/local/lib/roundcubemail
|
rm -rf /usr/local/lib/roundcubemail
|
||||||
mv /usr/local/lib/roundcubemail-$VERSION/ /usr/local/lib/roundcubemail
|
mv /usr/local/lib/roundcubemail-$VERSION/ $RCM_DIR
|
||||||
rm -f /tmp/roundcube.tgz
|
rm -f /tmp/roundcube.tgz
|
||||||
|
|
||||||
# install roundcube autoreply/vacation plugin
|
# install roundcube autoreply/vacation plugin
|
||||||
git_clone https://github.com/arodier/Roundcube-Plugins.git $VACATION_SIEVE_VERSION plugins/vacation_sieve /usr/local/lib/roundcubemail/plugins/vacation_sieve
|
git_clone https://github.com/arodier/Roundcube-Plugins.git $VACATION_SIEVE_VERSION plugins/vacation_sieve ${RCM_PLUGIN_DIR}/vacation_sieve
|
||||||
|
|
||||||
# install roundcube persistent_login plugin
|
# install roundcube persistent_login plugin
|
||||||
git_clone https://github.com/mfreiholz/Roundcube-Persistent-Login-Plugin.git $PERSISTENT_LOGIN_VERSION '' /usr/local/lib/roundcubemail/plugins/persistent_login
|
git_clone https://github.com/mfreiholz/Roundcube-Persistent-Login-Plugin.git $PERSISTENT_LOGIN_VERSION '' ${RCM_PLUGIN_DIR}/persistent_login
|
||||||
|
|
||||||
# install roundcube html5_notifier plugin
|
# install roundcube html5_notifier plugin
|
||||||
git_clone https://github.com/kitist/html5_notifier.git $HTML5_NOTIFIER_VERSION '' /usr/local/lib/roundcubemail/plugins/html5_notifier
|
git_clone https://github.com/kitist/html5_notifier.git $HTML5_NOTIFIER_VERSION '' ${RCM_PLUGIN_DIR}/html5_notifier
|
||||||
|
|
||||||
|
# download and verify the full release of the carddav plugin
|
||||||
|
wget_verify \
|
||||||
|
https://github.com/blind-coder/rcmcarddav/releases/download/v${CARDDAV_VERSION}/carddav-${CARDDAV_VERSION}.zip \
|
||||||
|
$CARDDAV_HASH \
|
||||||
|
/tmp/carddav.zip
|
||||||
|
|
||||||
|
# unzip and cleanup
|
||||||
|
unzip -q /tmp/carddav.zip -d ${RCM_PLUGIN_DIR}
|
||||||
|
rm -f /tmp/carddav.zip
|
||||||
|
|
||||||
# record the version we've installed
|
# record the version we've installed
|
||||||
echo $UPDATE_KEY > /usr/local/lib/roundcubemail/version
|
echo $UPDATE_KEY > ${RCM_DIR}/version
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ### Configuring Roundcube
|
# ### Configuring Roundcube
|
||||||
@@ -82,7 +101,7 @@ SECRET_KEY=$(dd if=/dev/urandom bs=1 count=18 2>/dev/null | base64 | fold -w 24
|
|||||||
# For security, temp and log files are not stored in the default locations
|
# For security, temp and log files are not stored in the default locations
|
||||||
# which are inside the roundcube sources directory. We put them instead
|
# which are inside the roundcube sources directory. We put them instead
|
||||||
# in normal places.
|
# in normal places.
|
||||||
cat > /usr/local/lib/roundcubemail/config/config.inc.php <<EOF;
|
cat > $RCM_CONFIG <<EOF;
|
||||||
<?php
|
<?php
|
||||||
/*
|
/*
|
||||||
* Do not edit. Written by Mail-in-a-Box. Regenerated on updates.
|
* Do not edit. Written by Mail-in-a-Box. Regenerated on updates.
|
||||||
@@ -101,14 +120,34 @@ cat > /usr/local/lib/roundcubemail/config/config.inc.php <<EOF;
|
|||||||
\$config['support_url'] = 'https://mailinabox.email/';
|
\$config['support_url'] = 'https://mailinabox.email/';
|
||||||
\$config['product_name'] = '$PRIMARY_HOSTNAME Webmail';
|
\$config['product_name'] = '$PRIMARY_HOSTNAME Webmail';
|
||||||
\$config['des_key'] = '$SECRET_KEY';
|
\$config['des_key'] = '$SECRET_KEY';
|
||||||
\$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'vacation_sieve', 'persistent_login');
|
\$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'vacation_sieve', 'persistent_login', 'carddav');
|
||||||
\$config['skin'] = 'classic';
|
\$config['skin'] = 'larry';
|
||||||
\$config['login_autocomplete'] = 2;
|
\$config['login_autocomplete'] = 2;
|
||||||
\$config['password_charset'] = 'UTF-8';
|
\$config['password_charset'] = 'UTF-8';
|
||||||
\$config['junk_mbox'] = 'Spam';
|
\$config['junk_mbox'] = 'Spam';
|
||||||
?>
|
?>
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
# Configure CardDav
|
||||||
|
cat > ${RCM_PLUGIN_DIR}/carddav/config.inc.php <<EOF;
|
||||||
|
<?php
|
||||||
|
/* Do not edit. Written by Mail-in-a-Box. Regenerated on updates. */
|
||||||
|
\$prefs['_GLOBAL']['hide_preferences'] = true;
|
||||||
|
\$prefs['_GLOBAL']['suppress_version_warning'] = true;
|
||||||
|
\$prefs['ownCloud'] = array(
|
||||||
|
'name' => 'ownCloud',
|
||||||
|
'username' => '%u', // login username
|
||||||
|
'password' => '%p', // login password
|
||||||
|
'url' => 'https://${PRIMARY_HOSTNAME}/cloud/remote.php/carddav/addressbooks/%u/contacts',
|
||||||
|
'active' => true,
|
||||||
|
'readonly' => false,
|
||||||
|
'refresh_time' => '02:00:00',
|
||||||
|
'fixed' => array('username','password'),
|
||||||
|
'preemptive_auth' => '1',
|
||||||
|
'hide' => false,
|
||||||
|
);
|
||||||
|
EOF
|
||||||
|
|
||||||
# Configure vaction_sieve.
|
# Configure vaction_sieve.
|
||||||
cat > /usr/local/lib/roundcubemail/plugins/vacation_sieve/config.inc.php <<EOF;
|
cat > /usr/local/lib/roundcubemail/plugins/vacation_sieve/config.inc.php <<EOF;
|
||||||
<?php
|
<?php
|
||||||
@@ -139,11 +178,11 @@ sudo -u www-data touch /var/log/roundcubemail/errors
|
|||||||
# Password changing plugin settings
|
# Password changing plugin settings
|
||||||
# The config comes empty by default, so we need the settings
|
# The config comes empty by default, so we need the settings
|
||||||
# we're not planning to change in config.inc.dist...
|
# we're not planning to change in config.inc.dist...
|
||||||
cp /usr/local/lib/roundcubemail/plugins/password/config.inc.php.dist \
|
cp ${RCM_PLUGIN_DIR}/password/config.inc.php.dist \
|
||||||
/usr/local/lib/roundcubemail/plugins/password/config.inc.php
|
${RCM_PLUGIN_DIR}/password/config.inc.php
|
||||||
|
|
||||||
tools/editconf.py /usr/local/lib/roundcubemail/plugins/password/config.inc.php \
|
tools/editconf.py ${RCM_PLUGIN_DIR}/password/config.inc.php \
|
||||||
"\$config['password_minimum_length']=6;" \
|
"\$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=%D WHERE email=%u';" \
|
||||||
"\$config['password_dovecotpw']='/usr/bin/doveadm pw';" \
|
"\$config['password_dovecotpw']='/usr/bin/doveadm pw';" \
|
||||||
@@ -160,8 +199,15 @@ 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:
|
||||||
|
chown -f -R root.www-data ${RCM_PLUGIN_DIR}/carddav
|
||||||
|
# root.www-data need all permissions, others only read
|
||||||
|
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)
|
||||||
/usr/local/lib/roundcubemail/bin/updatedb.sh --dir /usr/local/lib/roundcubemail/SQL --package roundcube
|
${RCM_DIR}/bin/updatedb.sh --dir ${RCM_DIR}/SQL --package roundcube
|
||||||
|
chown www-data:www-data $STORAGE_ROOT/mail/roundcube/roundcube.sqlite
|
||||||
|
chmod 664 $STORAGE_ROOT/mail/roundcube/roundcube.sqlite
|
||||||
|
|
||||||
# Enable PHP modules.
|
# Enable PHP modules.
|
||||||
php5enmod mcrypt
|
php5enmod mcrypt
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ apt_install \
|
|||||||
php5enmod imap
|
php5enmod imap
|
||||||
|
|
||||||
# Copy Z-Push into place.
|
# Copy Z-Push into place.
|
||||||
TARGETHASH=80cbe53de4ab8dd598d1f2af6f0a23fa396c529a
|
TARGETHASH=131229a8feda09782dfd06449adce3d5a219183f
|
||||||
|
VERSION=2.3.6
|
||||||
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
|
||||||
@@ -31,7 +32,13 @@ elif [[ $TARGETHASH != `cat /usr/local/lib/z-push/version` ]]; then
|
|||||||
needs_update=1 #NODOC
|
needs_update=1 #NODOC
|
||||||
fi
|
fi
|
||||||
if [ $needs_update == 1 ]; then
|
if [ $needs_update == 1 ]; then
|
||||||
git_clone https://github.com/fmbiete/Z-Push-contrib $TARGETHASH '' /usr/local/lib/z-push
|
wget_verify http://download.z-push.org/final/2.3/z-push-$VERSION.tar.gz $TARGETHASH /tmp/z-push.tar.gz
|
||||||
|
|
||||||
|
rm -rf /usr/local/lib/z-push
|
||||||
|
tar -xzf /tmp/z-push.tar.gz -C /usr/local/lib/
|
||||||
|
rm /tmp/z-push.tar.gz
|
||||||
|
mv /usr/local/lib/z-push-$VERSION /usr/local/lib/z-push
|
||||||
|
|
||||||
rm -f /usr/sbin/z-push-{admin,top}
|
rm -f /usr/sbin/z-push-{admin,top}
|
||||||
ln -s /usr/local/lib/z-push/z-push-admin.php /usr/sbin/z-push-admin
|
ln -s /usr/local/lib/z-push/z-push-admin.php /usr/sbin/z-push-admin
|
||||||
ln -s /usr/local/lib/z-push/z-push-top.php /usr/sbin/z-push-top
|
ln -s /usr/local/lib/z-push/z-push-top.php /usr/sbin/z-push-top
|
||||||
@@ -67,6 +74,7 @@ cp conf/zpush/backend_caldav.php /usr/local/lib/z-push/backend/caldav/config.php
|
|||||||
rm -f /usr/local/lib/z-push/autodiscover/config.php
|
rm -f /usr/local/lib/z-push/autodiscover/config.php
|
||||||
cp conf/zpush/autodiscover_config.php /usr/local/lib/z-push/autodiscover/config.php
|
cp conf/zpush/autodiscover_config.php /usr/local/lib/z-push/autodiscover/config.php
|
||||||
sed -i "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" /usr/local/lib/z-push/autodiscover/config.php
|
sed -i "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" /usr/local/lib/z-push/autodiscover/config.php
|
||||||
|
sed -i "s^define('TIMEZONE', .*^define('TIMEZONE', '$(cat /etc/timezone)');^" /usr/local/lib/z-push/autodiscover/config.php
|
||||||
|
|
||||||
# Some directories it will use.
|
# Some directories it will use.
|
||||||
|
|
||||||
@@ -93,3 +101,7 @@ EOF
|
|||||||
# Restart service.
|
# Restart service.
|
||||||
|
|
||||||
restart_service php5-fpm
|
restart_service php5-fpm
|
||||||
|
|
||||||
|
# Fix states after upgrade
|
||||||
|
|
||||||
|
hide_output z-push-admin -a fixstates
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ def mgmt(cmd, data=None, is_json=False):
|
|||||||
def read_password():
|
def read_password():
|
||||||
while True:
|
while True:
|
||||||
first = getpass.getpass('password: ')
|
first = getpass.getpass('password: ')
|
||||||
if len(first) < 4:
|
if len(first) < 8:
|
||||||
print("Passwords must be at least four characters.")
|
print("Passwords must be at least eight characters.")
|
||||||
continue
|
continue
|
||||||
if re.search(r'[\s]', first):
|
if re.search(r'[\s]', first):
|
||||||
print("Passwords cannot contain spaces.")
|
print("Passwords cannot contain spaces.")
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ fi
|
|||||||
echo "Restoring backup from $1"
|
echo "Restoring backup from $1"
|
||||||
service php5-fpm stop
|
service php5-fpm stop
|
||||||
|
|
||||||
# remove the current owncloud installation
|
# remove the current ownCloud/Nextcloud installation
|
||||||
rm -rf /usr/local/lib/owncloud/
|
rm -rf /usr/local/lib/owncloud/
|
||||||
# restore the current owncloud application
|
# restore the current ownCloud/Nextcloud application
|
||||||
cp -r "$1/owncloud-install" /usr/local/lib/owncloud
|
cp -r "$1/owncloud-install" /usr/local/lib/owncloud
|
||||||
|
|
||||||
# restore access rights
|
# restore access rights
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
#
|
#
|
||||||
# This script will give you administrative access to the ownCloud
|
# This script will give you administrative access to the Nextcloud
|
||||||
# instance running here.
|
# instance running here.
|
||||||
#
|
#
|
||||||
# Run this at your own risk. This is for testing & experimentation
|
# Run this at your own risk. This is for testing & experimentation
|
||||||
@@ -14,7 +14,7 @@ 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 owncloud\'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.
|
||||||
|
|||||||
Reference in New Issue
Block a user