mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2024-12-25 07:47:05 +00:00
Merge remote-tracking branch 'powermiab/master' into 20.04
This commit is contained in:
commit
7b82b3023c
@ -12,6 +12,9 @@ charset = utf-8
|
|||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.html]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
[Makefile]
|
[Makefile]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ externals/
|
|||||||
.env
|
.env
|
||||||
.vagrant
|
.vagrant
|
||||||
api/docs/api-docs.html
|
api/docs/api-docs.html
|
||||||
|
mailinabox-ca.crt
|
||||||
|
89
README.md
89
README.md
@ -1,5 +1,86 @@
|
|||||||
Mail-in-a-Box
|
(Power) Mail-in-a-Box
|
||||||
=============
|
=====================
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
- **PRE-REQUISITES:** Debian 10 (Buster) or Ubuntu 20.04 LTS fresh installation
|
||||||
|
|
||||||
|
Update packages:
|
||||||
|
```sh
|
||||||
|
sudo apt update
|
||||||
|
sudo apt full-upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure that the `en_US.UTF-8` locale exists and is set as primary (this depends on the image you use)
|
||||||
|
```sh
|
||||||
|
sudo apt install locales
|
||||||
|
sudo dpkg-reconfigure locales
|
||||||
|
```
|
||||||
|
|
||||||
|
Install Power-Mail-in-a-Box (short link)
|
||||||
|
```sh
|
||||||
|
curl -L https://dvn.pt/powermiab | sudo bash
|
||||||
|
```
|
||||||
|
|
||||||
|
If that doesn't work:
|
||||||
|
```sh
|
||||||
|
curl https://raw.githubusercontent.com/ddavness/power-mailinabox/master/setup/bootstrap.sh | sudo bash
|
||||||
|
```
|
||||||
|
|
||||||
|
## Current Version: v0.52.POWER.0 (Tracking v0.52)
|
||||||
|
|
||||||
|
This is a fork of MiaB (duh), hacked and tuned to my needs:
|
||||||
|
|
||||||
|
✅ - **Done**
|
||||||
|
|
||||||
|
👨💻 - **Not there yet, but soon!**
|
||||||
|
|
||||||
|
💤 - **I did not begin this part yet!**
|
||||||
|
|
||||||
|
- ✅ Support for Debian AND Ubuntu 20.04 LTS;
|
||||||
|
|
||||||
|
- ✅ Native support for SMTP relays (For example: SendGrid);
|
||||||
|
|
||||||
|
- ✅ Bumped the bootstrap and jQuery dependencies' versions - and we've got a brand new admin panel now!
|
||||||
|
|
||||||
|
- ✅ Per-domain `nginx` configuration: Custom pages will no longer have their pages defaulting to the MiaB services (`/admin`, `/mail`, etc.);
|
||||||
|
|
||||||
|
- ✅ Updated NextCloud to the latest version available;
|
||||||
|
|
||||||
|
- ✅ Performing backups immediately from the admin panel (independently from the daily schedule);
|
||||||
|
|
||||||
|
- 👨💻 Encrypting backups using user-provided PGP keys;
|
||||||
|
|
||||||
|
- 👨💻 Integrate a WKD server (Web Key Directory) for PGP keys;
|
||||||
|
|
||||||
|
- 💤 Restricting access to the admin panel to certain IP's?
|
||||||
|
|
||||||
|
- 💤 Customizing MTA names? (because privacy)
|
||||||
|
|
||||||
|
### Ideas section:
|
||||||
|
|
||||||
|
- 💤 Ability to download the backups from the admin panel;
|
||||||
|
|
||||||
|
- 💤 Possibility of making some services optional (if they require more software to be installed) on setup?
|
||||||
|
|
||||||
|
- - For example, one might simply not use NextCloud/Munin at all, and they're there... just wasting resources.
|
||||||
|
|
||||||
|
- 💤 AXFR Transfers (for secondary DNS) using TSIG?
|
||||||
|
|
||||||
|
- 💤 Expand DNS record options?
|
||||||
|
|
||||||
|
- 💤 More complete webmail configuration via the admin panel/plugin management?
|
||||||
|
|
||||||
|
- 💤 Expand the TOTP Two-Factor-Authentication for the webmail?
|
||||||
|
|
||||||
|
- - Maybe U2F one day, too, but I don't have a capable device for this just yet...
|
||||||
|
|
||||||
|
- 💤 Anything else I might need to use;
|
||||||
|
|
||||||
|
All in all, I think I should rename this to something like "Central [Clown Computing](https://www.urbandictionary.com/define.php?term=clown%20computing)", since I'm trying to cram as many services as possible into that poor machine (Spending 5$ is better than spending 10$)
|
||||||
|
|
||||||
|
Original Documentation
|
||||||
|
======================
|
||||||
|
|
||||||
By [@JoshData](https://github.com/JoshData) and [contributors](https://github.com/mail-in-a-box/mailinabox/graphs/contributors).
|
By [@JoshData](https://github.com/JoshData) and [contributors](https://github.com/mail-in-a-box/mailinabox/graphs/contributors).
|
||||||
|
|
||||||
@ -15,7 +96,7 @@ Our goals are to:
|
|||||||
* Promote [decentralization](http://redecentralize.org/), innovation, and privacy on the web.
|
* Promote [decentralization](http://redecentralize.org/), innovation, and privacy on the web.
|
||||||
* Have automated, auditable, and [idempotent](https://web.archive.org/web/20190518072631/https://sharknet.us/2014/02/01/automated-configuration-management-challenges-with-idempotency/) configuration.
|
* Have automated, auditable, and [idempotent](https://web.archive.org/web/20190518072631/https://sharknet.us/2014/02/01/automated-configuration-management-challenges-with-idempotency/) configuration.
|
||||||
* **Not** make a totally unhackable, NSA-proof server.
|
* **Not** make a totally unhackable, NSA-proof server.
|
||||||
* **Not** make something customizable by power users.
|
* ~~**Not** make something customizable by power users.~~
|
||||||
|
|
||||||
Additionally, this project has a [Code of Conduct](CODE_OF_CONDUCT.md), which supersedes the goals above. Please review it when joining our community.
|
Additionally, this project has a [Code of Conduct](CODE_OF_CONDUCT.md), which supersedes the goals above. Please review it when joining our community.
|
||||||
|
|
||||||
@ -23,7 +104,7 @@ Additionally, this project has a [Code of Conduct](CODE_OF_CONDUCT.md), which su
|
|||||||
In The Box
|
In The Box
|
||||||
----------
|
----------
|
||||||
|
|
||||||
Mail-in-a-Box turns a fresh Ubuntu 18.04 LTS 64-bit machine into a working mail server by installing and configuring various components.
|
Mail-in-a-Box turns a fresh ~~Ubuntu 18.04 LTS~~ Debian 10 (Buster) 64-bit machine into a working mail server by installing and configuring various components.
|
||||||
|
|
||||||
It is a one-click email appliance. There are no user-configurable setup options. It "just works."
|
It is a one-click email appliance. There are no user-configurable setup options. It "just works."
|
||||||
|
|
||||||
|
21
Vagrantfile
vendored
21
Vagrantfile
vendored
@ -1,8 +1,20 @@
|
|||||||
|
|
||||||
# -*- mode: ruby -*-
|
# -*- mode: ruby -*-
|
||||||
# vi: set ft=ruby :
|
# vi: set ft=ruby :
|
||||||
|
|
||||||
Vagrant.configure("2") do |config|
|
Vagrant.configure("2") do |config|
|
||||||
config.vm.box = "ubuntu/bionic64"
|
config.vm.box = "debian/buster64"
|
||||||
|
config.vm.provider :virtualbox do |vb|
|
||||||
|
vb.customize ["modifyvm", :id, "--cpus", 4, "--memory", 4096]
|
||||||
|
end
|
||||||
|
config.vm.provider :libvirt do |v|
|
||||||
|
v.memory = 4096
|
||||||
|
v.cpus = 4
|
||||||
|
v.nested = true
|
||||||
|
end
|
||||||
|
config.vm.provider :kvm do |kvm|
|
||||||
|
kvm.memory_size = '4096m'
|
||||||
|
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
|
||||||
@ -10,17 +22,18 @@ Vagrant.configure("2") do |config|
|
|||||||
# machine on a private network.
|
# machine on a private network.
|
||||||
config.vm.hostname = "mailinabox.lan"
|
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.synced_folder ".", "/vagrant", nfs_version: "3"
|
||||||
|
#, :mount_options => ["ro"]
|
||||||
|
|
||||||
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.
|
# machine figure out its own public IP.
|
||||||
export NONINTERACTIVE=1
|
export NONINTERACTIVE=1
|
||||||
export PUBLIC_IP=auto
|
export PUBLIC_IP=192.168.50.4
|
||||||
export PUBLIC_IPV6=auto
|
export PUBLIC_IPV6=auto
|
||||||
export PRIMARY_HOSTNAME=auto
|
export PRIMARY_HOSTNAME=auto
|
||||||
#export SKIP_NETWORK_CHECKS=1
|
export SKIP_NETWORK_CHECKS=1
|
||||||
|
|
||||||
# Start the setup script.
|
# Start the setup script.
|
||||||
cd /vagrant
|
cd /vagrant
|
||||||
setup/start.sh
|
setup/start.sh
|
||||||
|
@ -499,6 +499,123 @@ paths:
|
|||||||
text/html:
|
text/html:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
/system/backup/new:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- System
|
||||||
|
summary: Perform system backup
|
||||||
|
description: Performs a system backup.
|
||||||
|
operationId: performSystemBackup
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PerformBackupRequest'
|
||||||
|
examples:
|
||||||
|
incremental:
|
||||||
|
summary: Perform incremental backup.
|
||||||
|
value:
|
||||||
|
full: false
|
||||||
|
full:
|
||||||
|
summary: Force a full backup.
|
||||||
|
value:
|
||||||
|
full: true
|
||||||
|
x-codeSamples:
|
||||||
|
- lang: curl
|
||||||
|
source: |
|
||||||
|
curl -X POST "https://{host}/admin/system/backup/new" \
|
||||||
|
-d "full=<boolean>" \
|
||||||
|
-u "<email>:<password>"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successful operation
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PerformBackupResponse'
|
||||||
|
403:
|
||||||
|
description: Forbidden
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
/system/smtp/relay:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- System
|
||||||
|
summary: Get SMTP relay configuration
|
||||||
|
description: Gets basic configuration on how the box should use third-party relay services to deliver mail.
|
||||||
|
operationId: getRelayConfig
|
||||||
|
x-codeSamples:
|
||||||
|
- lang: curl
|
||||||
|
source: |
|
||||||
|
curl -X GET "https://{host}/admin/system/smtp/relay" \
|
||||||
|
-u "<email>:<password>"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successful operation
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SmtpRelayConfig'
|
||||||
|
403:
|
||||||
|
description: Forbidden
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- System
|
||||||
|
summary: Set SMTP relay configuration
|
||||||
|
description: Sets the configuration on how the box should use third-party relays to deliver mail.
|
||||||
|
operationId: setRelayConfig
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SetSmtpRelayConfigRequest'
|
||||||
|
examples:
|
||||||
|
disable:
|
||||||
|
summary: Do not use relays.
|
||||||
|
value:
|
||||||
|
enabled: false
|
||||||
|
host: ""
|
||||||
|
auth_enabled: false
|
||||||
|
user: ""
|
||||||
|
key: ""
|
||||||
|
no_auth:
|
||||||
|
summary: Use a relay that does not require authentication.
|
||||||
|
value:
|
||||||
|
enabled: true
|
||||||
|
host: smtp.relay.net
|
||||||
|
auth_enabled: false
|
||||||
|
user: ""
|
||||||
|
key: ""
|
||||||
|
auth:
|
||||||
|
summary: Use a relay that requires authentication.
|
||||||
|
value:
|
||||||
|
enabled: true
|
||||||
|
host: smtp.relay.net
|
||||||
|
auth_enabled: true
|
||||||
|
user: someuser
|
||||||
|
key: key-or-password-here
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successful operation
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
403:
|
||||||
|
description: Forbidden
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
|
||||||
/ssl/status:
|
/ssl/status:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -2459,6 +2576,19 @@ components:
|
|||||||
minimum: 1
|
minimum: 1
|
||||||
example: 3
|
example: 3
|
||||||
description: Backup config update request.
|
description: Backup config update request.
|
||||||
|
PerformBackupRequest:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- full
|
||||||
|
properties:
|
||||||
|
full:
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
|
description: New backup type.
|
||||||
|
PerformBackupResponse:
|
||||||
|
type: string
|
||||||
|
example: OK
|
||||||
|
description: Backup creation response.
|
||||||
SystemBackupConfigUpdateResponse:
|
SystemBackupConfigUpdateResponse:
|
||||||
type: string
|
type: string
|
||||||
example: OK
|
example: OK
|
||||||
@ -2661,6 +2791,52 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
example: web updated
|
example: web updated
|
||||||
description: Web update response.
|
description: Web update response.
|
||||||
|
SmtpRelayConfig:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- enabled
|
||||||
|
- host
|
||||||
|
- auth_enabled
|
||||||
|
- user
|
||||||
|
properties:
|
||||||
|
enabled:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
host:
|
||||||
|
type: string
|
||||||
|
example: sendgrid.net
|
||||||
|
auth_enabled:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
user:
|
||||||
|
type: string
|
||||||
|
example: someuser
|
||||||
|
description: SMTP configuration details.
|
||||||
|
SetSmtpRelayConfigRequest:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- enabled
|
||||||
|
- host
|
||||||
|
- auth_enabled
|
||||||
|
- user
|
||||||
|
- key
|
||||||
|
properties:
|
||||||
|
enabled:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
host:
|
||||||
|
type: string
|
||||||
|
example: sendgrid.net
|
||||||
|
auth_enabled:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
user:
|
||||||
|
type: string
|
||||||
|
example: apikey
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
example: 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
|
||||||
|
description: SMTP Configuration form
|
||||||
MfaStatusResponse:
|
MfaStatusResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Expose this directory as static files.
|
|
||||||
root $ROOT;
|
root $ROOT;
|
||||||
index index.html index.htm;
|
|
||||||
|
# ADDITIONAL DIRECTIVES HERE
|
||||||
|
|
||||||
location = /robots.txt {
|
location = /robots.txt {
|
||||||
log_not_found off;
|
log_not_found off;
|
||||||
@ -25,30 +25,6 @@
|
|||||||
alias /var/lib/mailinabox/mta-sts.txt;
|
alias /var/lib/mailinabox/mta-sts.txt;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Roundcube Webmail configuration.
|
|
||||||
rewrite ^/mail$ /mail/ redirect;
|
|
||||||
rewrite ^/mail/$ /mail/index.php;
|
|
||||||
location /mail/ {
|
|
||||||
index index.php;
|
|
||||||
alias /usr/local/lib/roundcubemail/;
|
|
||||||
}
|
|
||||||
location ~ /mail/config/.* {
|
|
||||||
# A ~-style location is needed to give this precedence over the next block.
|
|
||||||
return 403;
|
|
||||||
}
|
|
||||||
location ~ /mail/.*\.php {
|
|
||||||
# note: ~ has precendence over a regular location block
|
|
||||||
include fastcgi_params;
|
|
||||||
fastcgi_split_path_info ^/mail(/.*)()$;
|
|
||||||
fastcgi_index index.php;
|
|
||||||
fastcgi_param SCRIPT_FILENAME /usr/local/lib/roundcubemail/$fastcgi_script_name;
|
|
||||||
fastcgi_pass php-fpm;
|
|
||||||
|
|
||||||
# Outgoing mail also goes through this endpoint, so increase the maximum
|
|
||||||
# file upload limit to match the corresponding Postfix limit.
|
|
||||||
client_max_body_size 128M;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Z-Push (Microsoft Exchange ActiveSync)
|
# Z-Push (Microsoft Exchange ActiveSync)
|
||||||
location /Microsoft-Server-ActiveSync {
|
location /Microsoft-Server-ActiveSync {
|
||||||
include /etc/nginx/fastcgi_params;
|
include /etc/nginx/fastcgi_params;
|
||||||
@ -68,9 +44,6 @@
|
|||||||
fastcgi_pass php-fpm;
|
fastcgi_pass php-fpm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# ADDITIONAL DIRECTIVES HERE
|
|
||||||
|
|
||||||
# Disable viewing dotfiles (.htaccess, .svn, .git, etc.)
|
# Disable viewing dotfiles (.htaccess, .svn, .git, etc.)
|
||||||
# This block is placed at the end. Nginx's precedence rules means this block
|
# This block is placed at the end. Nginx's precedence rules means this block
|
||||||
# takes precedence over all non-regex matches and only regex matches that
|
# takes precedence over all non-regex matches and only regex matches that
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# ADDITIONAL DIRECTIVES HERE
|
||||||
|
|
||||||
# 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'.
|
||||||
@ -14,6 +16,30 @@
|
|||||||
add_header Content-Security-Policy "frame-ancestors 'none';";
|
add_header Content-Security-Policy "frame-ancestors 'none';";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Roundcube Webmail configuration.
|
||||||
|
rewrite ^/mail$ /mail/ redirect;
|
||||||
|
rewrite ^/mail/$ /mail/index.php;
|
||||||
|
location /mail/ {
|
||||||
|
index index.php;
|
||||||
|
alias /usr/local/lib/roundcubemail/;
|
||||||
|
}
|
||||||
|
location ~ /mail/config/.* {
|
||||||
|
# A ~-style location is needed to give this precedence over the next block.
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
location ~ /mail/.*\.php {
|
||||||
|
# note: ~ has precendence over a regular location block
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_split_path_info ^/mail(/.*)()$;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
fastcgi_param SCRIPT_FILENAME /usr/local/lib/roundcubemail/$fastcgi_script_name;
|
||||||
|
fastcgi_pass php-fpm;
|
||||||
|
|
||||||
|
# Outgoing mail also goes through this endpoint, so increase the maximum
|
||||||
|
# file upload limit to match the corresponding Postfix limit.
|
||||||
|
client_max_body_size 128M;
|
||||||
|
}
|
||||||
|
|
||||||
# Nextcloud configuration.
|
# Nextcloud configuration.
|
||||||
rewrite ^/cloud$ /cloud/ redirect;
|
rewrite ^/cloud$ /cloud/ redirect;
|
||||||
rewrite ^/cloud/$ /cloud/index.php;
|
rewrite ^/cloud/$ /cloud/index.php;
|
||||||
@ -72,5 +98,3 @@
|
|||||||
rewrite ^/.well-known/host-meta.json /cloud/public.php?service=host-meta-json last;
|
rewrite ^/.well-known/host-meta.json /cloud/public.php?service=host-meta-json last;
|
||||||
rewrite ^/.well-known/carddav /cloud/remote.php/carddav/ redirect;
|
rewrite ^/.well-known/carddav /cloud/remote.php/carddav/ redirect;
|
||||||
rewrite ^/.well-known/caldav /cloud/remote.php/caldav/ redirect;
|
rewrite ^/.well-known/caldav /cloud/remote.php/caldav/ redirect;
|
||||||
|
|
||||||
# ADDITIONAL DIRECTIVES HERE
|
|
||||||
|
@ -7,6 +7,5 @@
|
|||||||
## your own --- please do not ask for help from us.
|
## your own --- please do not ask for help from us.
|
||||||
|
|
||||||
upstream php-fpm {
|
upstream php-fpm {
|
||||||
server unix:/var/run/php/php7.2-fpm.sock;
|
server unix:/var/run/php/php{{phpver}}-fpm.sock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
|
||||||
<title>this is a mail-in-a-box</title>
|
<head>
|
||||||
|
<title>Redirecting...</title>
|
||||||
<meta name="robots" content="noindex">
|
<meta name="robots" content="noindex">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
|
||||||
<h1>this is a mail-in-a-box</h1>
|
<body>
|
||||||
<p>take control of your email at <a href="https://mailinabox.email/">https://mailinabox.email/</a></p>
|
</body>
|
||||||
</body>
|
|
||||||
|
<script>
|
||||||
|
location.href = "/mail"
|
||||||
|
</script>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
@ -10,9 +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 exclusiveprocess import Lock, CannotAcquireLock
|
||||||
|
|
||||||
from utils import load_environment, shell, wait_for_service, fix_boto
|
from utils import load_environment, shell, wait_for_service, fix_boto, get_php_version
|
||||||
|
|
||||||
rsync_ssh_options = [
|
rsync_ssh_options = [
|
||||||
"--ssh-options= -i /root/.ssh/id_rsa_miab",
|
"--ssh-options= -i /root/.ssh/id_rsa_miab",
|
||||||
@ -20,7 +20,7 @@ rsync_ssh_options = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
def backup_status(env):
|
def backup_status(env):
|
||||||
# If backups are dissbled, return no status.
|
# If backups are disabled, return no status.
|
||||||
config = get_backup_config(env)
|
config = get_backup_config(env)
|
||||||
if config["target"] == "off":
|
if config["target"] == "off":
|
||||||
return { }
|
return { }
|
||||||
@ -210,12 +210,21 @@ def get_target_type(config):
|
|||||||
protocol = config["target"].split(":")[0]
|
protocol = config["target"].split(":")[0]
|
||||||
return protocol
|
return protocol
|
||||||
|
|
||||||
def perform_backup(full_backup):
|
def perform_backup(full_backup, user_initiated=False):
|
||||||
env = load_environment()
|
env = load_environment()
|
||||||
|
php_fpm = f"php{get_php_version()}-fpm"
|
||||||
|
|
||||||
# Create an global exclusive lock so that the backup script
|
# Create an global exclusive lock so that the backup script
|
||||||
# cannot be run more than one.
|
# cannot be run more than one.
|
||||||
Lock(die=True).forever()
|
lock = Lock(name="mailinabox_backup_daemon", die=(not user_initiated))
|
||||||
|
if user_initiated:
|
||||||
|
# God forgive me for what I'm about to do
|
||||||
|
try:
|
||||||
|
lock._acquire()
|
||||||
|
except CannotAcquireLock:
|
||||||
|
return "Another backup is already being done!"
|
||||||
|
else:
|
||||||
|
lock.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')
|
||||||
@ -247,7 +256,7 @@ def perform_backup(full_backup):
|
|||||||
if quit:
|
if quit:
|
||||||
sys.exit(code)
|
sys.exit(code)
|
||||||
|
|
||||||
service_command("php7.2-fpm", "stop", quit=True)
|
service_command(php_fpm, "stop", quit=True)
|
||||||
service_command("postfix", "stop", quit=True)
|
service_command("postfix", "stop", quit=True)
|
||||||
service_command("dovecot", "stop", quit=True)
|
service_command("dovecot", "stop", quit=True)
|
||||||
|
|
||||||
@ -281,7 +290,7 @@ def perform_backup(full_backup):
|
|||||||
# Start services again.
|
# Start services again.
|
||||||
service_command("dovecot", "start", quit=False)
|
service_command("dovecot", "start", quit=False)
|
||||||
service_command("postfix", "start", quit=False)
|
service_command("postfix", "start", quit=False)
|
||||||
service_command("php7.2-fpm", "start", quit=False)
|
service_command(php_fpm, "start", quit=False)
|
||||||
|
|
||||||
# Remove old backups. This deletes all backup data no longer needed
|
# Remove old backups. This deletes all backup data no longer needed
|
||||||
# from more than 3 days ago.
|
# from more than 3 days ago.
|
||||||
@ -329,6 +338,11 @@ def perform_backup(full_backup):
|
|||||||
# backup. Since it checks that dovecot and postfix are running, block for a
|
# backup. Since it checks that dovecot and postfix are running, block for a
|
||||||
# bit (maximum of 10 seconds each) to give each a chance to finish restarting
|
# bit (maximum of 10 seconds each) to give each a chance to finish restarting
|
||||||
# before the status checks might catch them down. See #381.
|
# before the status checks might catch them down. See #381.
|
||||||
|
if user_initiated:
|
||||||
|
# God forgive me for what I'm about to do
|
||||||
|
lock._release()
|
||||||
|
# We don't need to wait for the services to be up in this case
|
||||||
|
else:
|
||||||
wait_for_service(25, True, env, 10)
|
wait_for_service(25, True, env, 10)
|
||||||
wait_for_service(993, True, env, 10)
|
wait_for_service(993, True, env, 10)
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ from flask import Flask, request, render_template, abort, Response, send_from_di
|
|||||||
|
|
||||||
import auth, utils
|
import auth, utils
|
||||||
from mailconfig import get_mail_users, get_mail_users_ex, get_admins, add_mail_user, set_mail_password, remove_mail_user
|
from mailconfig import get_mail_users, get_mail_users_ex, get_admins, add_mail_user, set_mail_password, remove_mail_user
|
||||||
from mailconfig import get_mail_user_privileges, add_remove_mail_user_privilege
|
from mailconfig import get_mail_user_privileges, add_remove_mail_user_privilege, open_database
|
||||||
from mailconfig import get_mail_aliases, get_mail_aliases_ex, get_mail_domains, add_mail_alias, remove_mail_alias
|
from mailconfig import get_mail_aliases, get_mail_aliases_ex, get_mail_domains, add_mail_alias, remove_mail_alias
|
||||||
from mfa import get_public_mfa_state, provision_totp, validate_totp_secret, enable_mfa, disable_mfa
|
from mfa import get_public_mfa_state, provision_totp, validate_totp_secret, enable_mfa, disable_mfa
|
||||||
|
|
||||||
@ -119,9 +119,12 @@ def index():
|
|||||||
utils.fix_boto() # must call prior to importing boto
|
utils.fix_boto() # must call prior to importing boto
|
||||||
import boto.s3
|
import boto.s3
|
||||||
backup_s3_hosts = [(r.name, r.endpoint) for r in boto.s3.regions()]
|
backup_s3_hosts = [(r.name, r.endpoint) for r in boto.s3.regions()]
|
||||||
|
lsb=utils.shell("check_output", ["/usr/bin/lsb_release", "-d"])
|
||||||
|
|
||||||
return render_template('index.html',
|
return render_template('index.html',
|
||||||
hostname=env['PRIMARY_HOSTNAME'],
|
hostname=env['PRIMARY_HOSTNAME'],
|
||||||
|
distname=lsb[lsb.find("\t")+1:-1],
|
||||||
|
|
||||||
storage_root=env['STORAGE_ROOT'],
|
storage_root=env['STORAGE_ROOT'],
|
||||||
|
|
||||||
no_users_exist=no_users_exist,
|
no_users_exist=no_users_exist,
|
||||||
@ -479,7 +482,10 @@ def web_get_domains():
|
|||||||
@authorized_personnel_only
|
@authorized_personnel_only
|
||||||
def web_update():
|
def web_update():
|
||||||
from web_update import do_web_update
|
from web_update import do_web_update
|
||||||
|
try:
|
||||||
return do_web_update(env)
|
return do_web_update(env)
|
||||||
|
except Exception as e:
|
||||||
|
return (str(e), 500)
|
||||||
|
|
||||||
# System
|
# System
|
||||||
|
|
||||||
@ -588,6 +594,19 @@ def backup_set_custom():
|
|||||||
request.form.get('min_age', '')
|
request.form.get('min_age', '')
|
||||||
))
|
))
|
||||||
|
|
||||||
|
@app.route('/system/backup/new', methods=["POST"])
|
||||||
|
@authorized_personnel_only
|
||||||
|
def backup_new():
|
||||||
|
from backup import perform_backup, get_backup_config
|
||||||
|
|
||||||
|
# If backups are disabled, don't perform the backup
|
||||||
|
config = get_backup_config(env)
|
||||||
|
if config["target"] == "off":
|
||||||
|
return "Backups are disabled in this machine. Nothing was done."
|
||||||
|
|
||||||
|
msg = perform_backup(request.form.get('full', False) == 'true', True)
|
||||||
|
return "OK" if msg is None else msg
|
||||||
|
|
||||||
@app.route('/system/privacy', methods=["GET"])
|
@app.route('/system/privacy', methods=["GET"])
|
||||||
@authorized_personnel_only
|
@authorized_personnel_only
|
||||||
def privacy_status_get():
|
def privacy_status_get():
|
||||||
@ -602,6 +621,49 @@ def privacy_status_set():
|
|||||||
utils.write_settings(config, env)
|
utils.write_settings(config, env)
|
||||||
return "OK"
|
return "OK"
|
||||||
|
|
||||||
|
@app.route('/system/smtp/relay', methods=["GET"])
|
||||||
|
@authorized_personnel_only
|
||||||
|
def smtp_relay_get():
|
||||||
|
config = utils.load_settings(env)
|
||||||
|
return {
|
||||||
|
"enabled": config.get("SMTP_RELAY_ENABLED", True),
|
||||||
|
"host": config.get("SMTP_RELAY_HOST", ""),
|
||||||
|
"auth_enabled": config.get("SMTP_RELAY_AUTH", False),
|
||||||
|
"user": config.get("SMTP_RELAY_USER", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.route('/system/smtp/relay', methods=["POST"])
|
||||||
|
@authorized_personnel_only
|
||||||
|
def smtp_relay_set():
|
||||||
|
from editconf import edit_conf
|
||||||
|
config = utils.load_settings(env)
|
||||||
|
newconf = request.form
|
||||||
|
try:
|
||||||
|
# Write on daemon settings
|
||||||
|
config["SMTP_RELAY_ENABLED"] = (newconf.get("enabled") == "true")
|
||||||
|
config["SMTP_RELAY_HOST"] = newconf.get("host")
|
||||||
|
config["SMTP_RELAY_AUTH"] = (newconf.get("auth_enabled") == "true")
|
||||||
|
config["SMTP_RELAY_USER"] = newconf.get("user")
|
||||||
|
utils.write_settings(config, env)
|
||||||
|
# Write on Postfix configs
|
||||||
|
edit_conf("/etc/postfix/main.cf", [
|
||||||
|
"relayhost=" + (f"[{config['SMTP_RELAY_HOST']}]:587" if config["SMTP_RELAY_ENABLED"] else ""),
|
||||||
|
"smtp_sasl_auth_enable=" + ("yes" if config["SMTP_RELAY_AUTH"] else "no"),
|
||||||
|
"smtp_sasl_security_options=" + ("noanonymous" if config["SMTP_RELAY_AUTH"] else "anonymous"),
|
||||||
|
"smtp_sasl_tls_security_options=" + ("noanonymous" if config["SMTP_RELAY_AUTH"] else "anonymous")
|
||||||
|
], delimiter_re=r"\s*=\s*", delimiter="=", comment_char="#")
|
||||||
|
if config["SMTP_RELAY_AUTH"]:
|
||||||
|
# Edit the sasl password
|
||||||
|
with open("/etc/postfix/sasl_passwd", "w") as f:
|
||||||
|
f.write(f"[{config['SMTP_RELAY_HOST']}]:587 {config['SMTP_RELAY_USER']}:{newconf.get('key')}\n")
|
||||||
|
utils.shell("check_output", ["/usr/bin/chmod", "600", "/etc/postfix/sasl_passwd"], capture_stderr=True)
|
||||||
|
utils.shell("check_output", ["/usr/sbin/postmap", "/etc/postfix/sasl_passwd"], capture_stderr=True)
|
||||||
|
# Restart Postfix
|
||||||
|
return utils.shell("check_output", ["/usr/bin/systemctl", "restart", "postfix"], capture_stderr=True)
|
||||||
|
except Exception as e:
|
||||||
|
return (str(e), 500)
|
||||||
|
|
||||||
|
|
||||||
# MUNIN
|
# MUNIN
|
||||||
|
|
||||||
@app.route('/munin/')
|
@app.route('/munin/')
|
||||||
|
@ -947,9 +947,9 @@ def get_secondary_dns(custom_dns, mode=None):
|
|||||||
# doesn't.
|
# doesn't.
|
||||||
if not hostname.startswith("xfr:"):
|
if not hostname.startswith("xfr:"):
|
||||||
if mode == "xfr":
|
if mode == "xfr":
|
||||||
response = dns.resolver.query(hostname+'.', "A", raise_on_no_answer=False)
|
response = dns.resolver.resolve(hostname+'.', "A", raise_on_no_answer=False)
|
||||||
values.extend(map(str, response))
|
values.extend(map(str, response))
|
||||||
response = dns.resolver.query(hostname+'.', "AAAA", raise_on_no_answer=False)
|
response = dns.resolver.resolve(hostname+'.', "AAAA", raise_on_no_answer=False)
|
||||||
values.extend(map(str, response))
|
values.extend(map(str, response))
|
||||||
continue
|
continue
|
||||||
values.append(hostname)
|
values.append(hostname)
|
||||||
@ -972,7 +972,7 @@ def set_secondary_dns(hostnames, env):
|
|||||||
if not item.startswith("xfr:"):
|
if not item.startswith("xfr:"):
|
||||||
# Resolve hostname.
|
# Resolve hostname.
|
||||||
try:
|
try:
|
||||||
response = resolver.query(item, "A")
|
response = resolver.resolve(item, "A")
|
||||||
except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
|
except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
|
||||||
try:
|
try:
|
||||||
response = resolver.query(item, "AAAA")
|
response = resolver.query(item, "AAAA")
|
||||||
|
143
management/editconf.py
Executable file
143
management/editconf.py
Executable file
@ -0,0 +1,143 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
#
|
||||||
|
# This is a helper tool for editing configuration files during the setup
|
||||||
|
# process. The tool is given new values for settings as command-line
|
||||||
|
# arguments. It comments-out existing setting values in the configuration
|
||||||
|
# file and adds new values either after their former location or at the
|
||||||
|
# end.
|
||||||
|
#
|
||||||
|
# The configuration file has settings that look like:
|
||||||
|
#
|
||||||
|
# NAME=VALUE
|
||||||
|
#
|
||||||
|
# If the -s option is given, then space becomes the delimiter, i.e.:
|
||||||
|
#
|
||||||
|
# NAME VALUE
|
||||||
|
#
|
||||||
|
# If the -c option is given, then the supplied character becomes the comment character
|
||||||
|
#
|
||||||
|
# If the -w option is given, then setting lines continue onto following
|
||||||
|
# lines while the lines start with whitespace, e.g.:
|
||||||
|
#
|
||||||
|
# NAME VAL
|
||||||
|
# UE
|
||||||
|
|
||||||
|
# create the new config file in memory
|
||||||
|
|
||||||
|
import sys, re
|
||||||
|
|
||||||
|
def edit_conf(filename, settings, delimiter_re, delimiter, comment_char, folded_lines = False, testing = False):
|
||||||
|
found = set()
|
||||||
|
buf = ""
|
||||||
|
input_lines = list(open(filename, "r+"))
|
||||||
|
|
||||||
|
while len(input_lines) > 0:
|
||||||
|
line = input_lines.pop(0)
|
||||||
|
|
||||||
|
# If this configuration file uses folded lines, append any folded lines
|
||||||
|
# into our input buffer.
|
||||||
|
if folded_lines and line[0] not in (comment_char, " ", ""):
|
||||||
|
while len(input_lines) > 0 and input_lines[0][0] in " \t":
|
||||||
|
line += input_lines.pop(0)
|
||||||
|
|
||||||
|
# See if this line is for any settings passed on the command line.
|
||||||
|
for i in range(len(settings)):
|
||||||
|
# Check that this line contain this setting from the command-line arguments.
|
||||||
|
name, val = settings[i].split("=", 1)
|
||||||
|
m = re.match(
|
||||||
|
"(\s*)"
|
||||||
|
+ "(" + re.escape(comment_char) + "\s*)?"
|
||||||
|
+ re.escape(name) + delimiter_re + "(.*?)\s*$",
|
||||||
|
line, re.S)
|
||||||
|
if not m: continue
|
||||||
|
indent, is_comment, existing_val = m.groups()
|
||||||
|
|
||||||
|
# If this is already the setting, do nothing.
|
||||||
|
if is_comment is None and existing_val == val:
|
||||||
|
# It may be that we've already inserted this setting higher
|
||||||
|
# in the file so check for that first.
|
||||||
|
if i in found: break
|
||||||
|
buf += line
|
||||||
|
found.add(i)
|
||||||
|
break
|
||||||
|
|
||||||
|
# comment-out the existing line (also comment any folded lines)
|
||||||
|
if is_comment is None:
|
||||||
|
buf += comment_char + line.rstrip().replace("\n", "\n" + comment_char) + "\n"
|
||||||
|
else:
|
||||||
|
# the line is already commented, pass it through
|
||||||
|
buf += line
|
||||||
|
|
||||||
|
# if this option oddly appears more than once, don't add the setting again
|
||||||
|
if i in found:
|
||||||
|
break
|
||||||
|
|
||||||
|
# add the new setting
|
||||||
|
buf += indent + name + delimiter + val + "\n"
|
||||||
|
|
||||||
|
# note that we've applied this option
|
||||||
|
found.add(i)
|
||||||
|
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# If did not match any setting names, pass this line through.
|
||||||
|
buf += line
|
||||||
|
|
||||||
|
# Put any settings we didn't see at the end of the file.
|
||||||
|
for i in range(len(settings)):
|
||||||
|
if i not in found:
|
||||||
|
name, val = settings[i].split("=", 1)
|
||||||
|
buf += name + delimiter + val + "\n"
|
||||||
|
|
||||||
|
if not testing:
|
||||||
|
# Write out the new file.
|
||||||
|
with open(filename, "w") as f:
|
||||||
|
f.write(buf)
|
||||||
|
else:
|
||||||
|
# Just print the new file to stdout.
|
||||||
|
print(buf)
|
||||||
|
|
||||||
|
# Run standalone
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# sanity check
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print("usage: python3 editconf.py /etc/file.conf [-s] [-w] [-c <CHARACTER>] [-t] NAME=VAL [NAME=VAL ...]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# parse command line arguments
|
||||||
|
filename = sys.argv[1]
|
||||||
|
settings = sys.argv[2:]
|
||||||
|
|
||||||
|
delimiter = "="
|
||||||
|
delimiter_re = r"\s*=\s*"
|
||||||
|
comment_char = "#"
|
||||||
|
folded_lines = False
|
||||||
|
testing = False
|
||||||
|
while settings[0][0] == "-" and settings[0] != "--":
|
||||||
|
opt = settings.pop(0)
|
||||||
|
if opt == "-s":
|
||||||
|
# Space is the delimiter
|
||||||
|
delimiter = " "
|
||||||
|
delimiter_re = r"\s+"
|
||||||
|
elif opt == "-w":
|
||||||
|
# Line folding is possible in this file.
|
||||||
|
folded_lines = True
|
||||||
|
elif opt == "-c":
|
||||||
|
# Specifies a different comment character.
|
||||||
|
comment_char = settings.pop(0)
|
||||||
|
elif opt == "-t":
|
||||||
|
testing = True
|
||||||
|
else:
|
||||||
|
print("Invalid option.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# sanity check command line
|
||||||
|
for setting in settings:
|
||||||
|
try:
|
||||||
|
name, value = setting.split("=", 1)
|
||||||
|
except:
|
||||||
|
import subprocess
|
||||||
|
print("Invalid command line: ", subprocess.list2cmdline(sys.argv))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
edit_conf(filename, settings, delimiter_re, delimiter, comment_char, folded_lines, testing)
|
@ -346,6 +346,8 @@ def provision_certificates(env, limit_domains):
|
|||||||
"certonly",
|
"certonly",
|
||||||
#"-v", # just enough to see ACME errors
|
#"-v", # just enough to see ACME errors
|
||||||
"--non-interactive", # will fail if user hasn't registered during Mail-in-a-Box setup
|
"--non-interactive", # will fail if user hasn't registered during Mail-in-a-Box setup
|
||||||
|
"--agree-tos", # Automatically agrees to Let's Encrypt TOS
|
||||||
|
"--register-unsafely-without-email", # The daemon takes care of renewals
|
||||||
|
|
||||||
"-d", ",".join(domain_list), # first will be main domain
|
"-d", ",".join(domain_list), # first will be main domain
|
||||||
|
|
||||||
|
@ -280,9 +280,9 @@ def run_network_checks(env, output):
|
|||||||
if ret == 0:
|
if ret == 0:
|
||||||
output.print_ok("Outbound mail (SMTP port 25) is not blocked.")
|
output.print_ok("Outbound mail (SMTP port 25) is not blocked.")
|
||||||
else:
|
else:
|
||||||
output.print_error("""Outbound mail (SMTP port 25) seems to be blocked by your network. You
|
output.print_warning("""Outbound mail (SMTP port 25) seems to be blocked by your network. You
|
||||||
will not be able to send any mail. Many residential networks block port 25 to prevent hijacked
|
will not be able to send any mail without a SMTP relay. Many residential networks block port 25 to prevent
|
||||||
machines from being able to send spam. A quick connection test to Google's mail server on port 25
|
hijacked machines from being able to send spam. A quick connection test to Google's mail server on port 25
|
||||||
failed.""")
|
failed.""")
|
||||||
|
|
||||||
# Stop if the IPv4 address is listed in the ZEN Spamhaus Block List.
|
# Stop if the IPv4 address is listed in the ZEN Spamhaus Block List.
|
||||||
@ -300,6 +300,19 @@ def run_network_checks(env, output):
|
|||||||
which may prevent recipients from receiving your email. See http://www.spamhaus.org/query/ip/%s."""
|
which may prevent recipients from receiving your email. See http://www.spamhaus.org/query/ip/%s."""
|
||||||
% (env['PUBLIC_IP'], zen, env['PUBLIC_IP']))
|
% (env['PUBLIC_IP'], zen, env['PUBLIC_IP']))
|
||||||
|
|
||||||
|
# Check if a SMTP relay is set up. It's not strictly required, but on some providers
|
||||||
|
# it might be needed.
|
||||||
|
config = load_settings(env)
|
||||||
|
if config.get("SMTP_RELAY_ENABLED"):
|
||||||
|
if config.get("SMTP_RELAY_AUTH"):
|
||||||
|
output.print_ok("An authenticated SMTP relay has been set up via port 587.")
|
||||||
|
else:
|
||||||
|
output.print_warning("A SMTP relay has been set up, but it is not authenticated.")
|
||||||
|
elif ret == 0:
|
||||||
|
output.print_ok("No SMTP relay has been set up (but that's ok since port 25 is not blocked).")
|
||||||
|
else:
|
||||||
|
output.print_error("No SMTP relay has been set up. Since port 25 is blocked, you will probably not be able to send any mail.")
|
||||||
|
|
||||||
def run_domain_checks(rounded_time, env, output, pool):
|
def run_domain_checks(rounded_time, env, output, pool):
|
||||||
# Get the list of domains we handle mail for.
|
# Get the list of domains we handle mail for.
|
||||||
mail_domains = get_mail_domains(env)
|
mail_domains = get_mail_domains(env)
|
||||||
@ -735,7 +748,7 @@ def query_dns(qname, rtype, nxdomain='[Not Set]', at=None):
|
|||||||
|
|
||||||
# Do the query.
|
# Do the query.
|
||||||
try:
|
try:
|
||||||
response = resolver.query(qname, rtype)
|
response = resolver.resolve(qname, rtype)
|
||||||
except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
|
except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
|
||||||
# Host did not have an answer for this query; not sure what the
|
# Host did not have an answer for this query; not sure what the
|
||||||
# difference is between the two exceptions.
|
# difference is between the two exceptions.
|
||||||
@ -834,7 +847,7 @@ def what_version_is_this(env):
|
|||||||
# Git may not be installed and Mail-in-a-Box may not have been cloned from github,
|
# Git may not be installed and Mail-in-a-Box may not have been cloned from github,
|
||||||
# so this function may raise all sorts of exceptions.
|
# so this function may raise all sorts of exceptions.
|
||||||
miab_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
miab_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
tag = shell("check_output", ["/usr/bin/git", "describe", "--abbrev=0"], env={"GIT_DIR": os.path.join(miab_dir, '.git')}).strip()
|
tag = shell("check_output", ["/usr/bin/git", "describe", "--tags", "--abbrev=0"], env={"GIT_DIR": os.path.join(miab_dir, '.git')}).strip()
|
||||||
return tag
|
return tag
|
||||||
|
|
||||||
def get_latest_miab_version():
|
def get_latest_miab_version():
|
||||||
@ -844,7 +857,7 @@ def get_latest_miab_version():
|
|||||||
from socket import timeout
|
from socket import timeout
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return re.search(b'TAG=(.*)', urlopen("https://mailinabox.email/setup.sh?ping=1", timeout=5).read()).group(1).decode("utf8")
|
return re.search(b'TAG=(.*)', urlopen("https://raw.githubusercontent.com/ddavness/power-mailinabox/master/setup/bootstrap.sh", timeout=5).read()).group(1).decode("utf8")
|
||||||
except (HTTPError, URLError, timeout):
|
except (HTTPError, URLError, timeout):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -94,10 +94,10 @@
|
|||||||
<tr id="alias-template">
|
<tr id="alias-template">
|
||||||
<td class='actions'>
|
<td class='actions'>
|
||||||
<a href="#" onclick="aliases_edit(this); scroll_top(); return false;" class='edit' title="Edit Alias">
|
<a href="#" onclick="aliases_edit(this); scroll_top(); return false;" class='edit' title="Edit Alias">
|
||||||
<span class="glyphicon glyphicon-pencil"></span>
|
<span class="fas fa-pen"></span>
|
||||||
</a>
|
</a>
|
||||||
<a href="#" onclick="aliases_remove(this); return false;" class='remove' title="Remove Alias">
|
<a href="#" onclick="aliases_remove(this); return false;" class='remove' title="Remove Alias">
|
||||||
<span class="glyphicon glyphicon-trash"></span>
|
<span class="fas fa-trash"></span>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class='address'> </td>
|
<td class='address'> </td>
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<p class="alert" role="alert">
|
<p class="alert" role="alert">
|
||||||
<span class="glyphicon glyphicon-info-sign"></span>
|
<span class="fas fa-info-circle"></span>
|
||||||
You may encounter zone file errors when attempting to create a TXT record with a long string.
|
You may encounter zone file errors when attempting to create a TXT record with a long string.
|
||||||
<a href="http://tools.ietf.org/html/rfc4408#section-3.1.3">RFC 4408</a> states a TXT record is allowed to contain multiple strings, and this technique can be used to construct records that would exceed the 255-byte maximum length.
|
<a href="http://tools.ietf.org/html/rfc4408#section-3.1.3">RFC 4408</a> states a TXT record is allowed to contain multiple strings, and this technique can be used to construct records that would exceed the 255-byte maximum length.
|
||||||
You may need to adopt this technique when adding DomainKeys. Use a tool like <code>named-checkzone</code> to validate your zone file.
|
You may need to adopt this technique when adding DomainKeys. Use a tool like <code>named-checkzone</code> to validate your zone file.
|
||||||
@ -50,7 +50,7 @@
|
|||||||
<label for="downloadZonefile" class="control-label sr-only">Zone</label>
|
<label for="downloadZonefile" class="control-label sr-only">Zone</label>
|
||||||
<select id="downloadZonefile" class="form-control" style="width: auto"> </select>
|
<select id="downloadZonefile" class="form-control" style="width: auto"> </select>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Download</button>
|
<button type="submit" style="margin-left: 20px" class="btn btn-primary">Download</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -10,6 +10,9 @@
|
|||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
|
||||||
<link rel="stylesheet" href="/admin/assets/bootstrap/css/bootstrap.min.css">
|
<link rel="stylesheet" href="/admin/assets/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/admin/assets/fontawesome/css/fontawesome.min.css">
|
||||||
|
<link rel="stylesheet" href="/admin/assets/fontawesome/css/solid.min.css">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
@ -63,7 +66,6 @@
|
|||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="/admin/assets/bootstrap/css/bootstrap-theme.min.css">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@ -71,53 +73,52 @@
|
|||||||
<!--[if gt IE 7]><!-->
|
<!--[if gt IE 7]><!-->
|
||||||
|
|
||||||
<div class="navbar navbar-inverse navbar-static-top" role="navigation">
|
<div class="navbar navbar-inverse navbar-static-top" role="navigation">
|
||||||
<div class="container">
|
<div class="container bg-light">
|
||||||
<div class="navbar-header">
|
<div class="navbar-header">
|
||||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
|
|
||||||
<span class="sr-only">Toggle navigation</span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
</button>
|
|
||||||
<a class="navbar-brand" href="#">{{hostname}}</a>
|
<a class="navbar-brand" href="#">{{hostname}}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-collapse collapse">
|
<div class="navbar navbar-expand-lg">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
<li class="dropdown">
|
<li class="btn dropdown">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">System <b class="caret"></b></a>
|
<a style="color: black;" href="#" class="dropdown-toggle" data-toggle="dropdown">System <b class="caret"></b></a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a href="#system_status" onclick="return show_panel(this);">Status Checks</a></li>
|
<li class="dropdown-item"><a href="#system_status" onclick="return show_panel(this);">Status Checks</a></li>
|
||||||
<li><a href="#tls" onclick="return show_panel(this);">TLS (SSL) Certificates</a></li>
|
<li class="dropdown-item"><a href="#tls" onclick="return show_panel(this);">TLS (SSL) Certificates</a></li>
|
||||||
<li><a href="#system_backup" onclick="return show_panel(this);">Backup Status</a></li>
|
<li class="dropdown-item"><a href="#system_backup" onclick="return show_panel(this);">Backup Status</a></li>
|
||||||
|
<li class="dropdown-item"><a href="#smtp_relays" onclick="return show_panel(this);">SMTP Relays</a></li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li class="dropdown-header">Advanced Pages</li>
|
<li class="dropdown-header">Advanced Pages</li>
|
||||||
<li><a href="#custom_dns" onclick="return show_panel(this);">Custom DNS</a></li>
|
<li class="dropdown-item"><a href="#custom_dns" onclick="return show_panel(this);">Custom DNS</a></li>
|
||||||
<li><a href="#external_dns" onclick="return show_panel(this);">External DNS</a></li>
|
<li class="dropdown-item"><a href="#external_dns" onclick="return show_panel(this);">External DNS</a></li>
|
||||||
<li><a href="/admin/munin" target="_blank">Munin Monitoring</a></li>
|
<li class="dropdown-item"><a href="/admin/munin" target="_blank">Munin Monitoring</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown">
|
<li class="btn dropdown">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Mail & Users <b class="caret"></b></a>
|
<a style="color: black;" href="#" class="dropdown-toggle" data-toggle="dropdown">Mail & Users <b class="caret"></b></a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a href="#mail-guide" onclick="return show_panel(this);">Instructions</a></li>
|
<li class="dropdown-item"><a href="#mail-guide" onclick="return show_panel(this);">Instructions</a></li>
|
||||||
<li><a href="#users" onclick="return show_panel(this);">Users</a></li>
|
<li class="dropdown-item"><a href="#users" onclick="return show_panel(this);">Users</a></li>
|
||||||
<li><a href="#aliases" onclick="return show_panel(this);">Aliases</a></li>
|
<li class="dropdown-item"><a href="#aliases" onclick="return show_panel(this);">Aliases</a></li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li class="dropdown-header">Your Account</li>
|
<li class="dropdown-header">Your Account</li>
|
||||||
<li><a href="#mfa" onclick="return show_panel(this);">Two-Factor Authentication</a></li>
|
<li class="dropdown-item"><a href="#mfa" onclick="return show_panel(this);">Two-Factor Authentication</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#sync_guide" onclick="return show_panel(this);">Contacts/Calendar</a></li>
|
<li class="btn"><a style="color: black;" href="#sync_guide" onclick="return show_panel(this);">Contacts/Calendar</a></li>
|
||||||
<li><a href="#web" onclick="return show_panel(this);">Web</a></li>
|
<li class="btn"><a style="color: black;" href="#web" onclick="return show_panel(this);">Web</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="btn nav navbar-nav navbar-right">
|
||||||
<li><a href="#" onclick="do_logout(); return false;" style="color: white">Log out</a></li>
|
<li><a href="#" onclick="do_logout(); return false;" style="color: black; font-weight: bold;">Log out</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div><!--/.navbar-collapse -->
|
</div><!--/.navbar-collapse -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<div id="panel_smtp_relays" class="admin_panel">
|
||||||
|
{% include "smtp-relays.html" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="panel_system_status" class="admin_panel">
|
<div id="panel_system_status" class="admin_panel">
|
||||||
{% include "system-status.html" %}
|
{% include "system-status.html" %}
|
||||||
</div>
|
</div>
|
||||||
@ -169,7 +170,7 @@
|
|||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>This is a <a href="https://mailinabox.email">Mail-in-a-Box</a>.</p>
|
<p>This is a <a href="https://github.com/ddavness/power-mailinabox">Power Mail-in-a-Box</a> - {{distname}}</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div> <!-- /container -->
|
</div> <!-- /container -->
|
||||||
|
|
||||||
@ -184,8 +185,8 @@
|
|||||||
<div class="modal-dialog modal-sm">
|
<div class="modal-dialog modal-sm">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
|
||||||
<h4 class="modal-title" id="errorModalTitle"> </h4>
|
<h4 class="modal-title" id="errorModalTitle"> </h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p> </p>
|
<p> </p>
|
||||||
|
@ -51,19 +51,19 @@ sudo management/cli.py user make-admin me@{{hostname}}</pre>
|
|||||||
<form id="loginForm" class="form-horizontal" role="form" onsubmit="do_login(); return false;" method="get">
|
<form id="loginForm" class="form-horizontal" role="form" onsubmit="do_login(); return false;" method="get">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputEmail3" class="col-sm-3 control-label">Email</label>
|
<label for="inputEmail3" class="col-sm-3 control-label">Email</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-12">
|
||||||
<input name="email" type="email" class="form-control" id="loginEmail" placeholder="Email">
|
<input name="email" type="email" class="form-control" id="loginEmail" placeholder="Email">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputPassword3" class="col-sm-3 control-label">Password</label>
|
<label for="inputPassword3" class="col-sm-3 control-label">Password</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-12">
|
||||||
<input name="password" type="password" class="form-control" id="loginPassword" placeholder="Password">
|
<input name="password" type="password" class="form-control" id="loginPassword" placeholder="Password">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" id="loginOtp">
|
<div class="form-group" id="loginOtp">
|
||||||
<label for="loginOtpInput" class="col-sm-3 control-label">Code</label>
|
<label for="loginOtpInput" class="col-sm-3 control-label">Code</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-12">
|
||||||
<input type="text" class="form-control" id="loginOtpInput" placeholder="6-digit code">
|
<input type="text" class="form-control" id="loginOtpInput" placeholder="6-digit code">
|
||||||
<div class="help-block" style="margin-top: 5px; font-size: 90%">Enter the six-digit code generated by your two factor authentication app.</div>
|
<div class="help-block" style="margin-top: 5px; font-size: 90%">Enter the six-digit code generated by your two factor authentication app.</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,11 +36,11 @@
|
|||||||
<p>When two-factor authentication is enabled, you will be prompted to enter a six digit code from an
|
<p>When two-factor authentication is enabled, you will be prompted to enter a six digit code from an
|
||||||
authenticator app (usually on your phone) when you log into this control panel.</p>
|
authenticator app (usually on your phone) when you log into this control panel.</p>
|
||||||
|
|
||||||
<div class="panel panel-danger">
|
<div class="card">
|
||||||
<div class="panel-heading">
|
<div class="card-header text-white bg-danger">
|
||||||
Enabling two-factor authentication does not protect access to your email
|
Enabling two-factor authentication does not protect access to your email
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="card-body bg-light">
|
||||||
Enabling two-factor authentication on this page only limits access to this control panel. Remember that most websites allow you to
|
Enabling two-factor authentication on this page only limits access to this control panel. Remember that most websites allow you to
|
||||||
reset your password by checking your email, so anyone with access to your email can typically take over
|
reset your password by checking your email, so anyone with access to your email can typically take over
|
||||||
your other accounts. Additionally, if your email address or any alias that forwards to your email
|
your other accounts. Additionally, if your email address or any alias that forwards to your email
|
||||||
@ -81,7 +81,7 @@ and ensure every administrator account for this control panel does the same.</st
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<p>When you click Enable Two-Factor Authentication, you will be logged out of the control panel and will have to log in
|
<p>When you click Enable Two-Factor Authentication, you will be logged out of the control panel and will have to log in
|
||||||
again, now using your two-factor authentication app.</p>
|
again, now using your two-factor authentication app.</p>
|
||||||
<button id="totp-setup-submit" disabled type="submit" class="btn">Enable Two-Factor Authentication</button>
|
<button id="totp-setup-submit" disabled type="submit" class="btn btn-primary">Enable Two-Factor Authentication</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@ -95,8 +95,8 @@ and ensure every administrator account for this control panel does the same.</st
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div id="output-2fa" class="panel panel-danger">
|
<div id="output-2fa" class="card bg-light">
|
||||||
<div class="panel-body"></div>
|
<div class="card-body"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -155,12 +155,12 @@ and ensure every administrator account for this control panel does the same.</st
|
|||||||
}
|
}
|
||||||
|
|
||||||
function hide_error() {
|
function hide_error() {
|
||||||
el.output.querySelector('.panel-body').innerHTML = '';
|
el.output.querySelector('.card-body').innerHTML = '';
|
||||||
el.output.classList.remove('visible');
|
el.output.classList.remove('visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
function render_error(msg) {
|
function render_error(msg) {
|
||||||
el.output.querySelector('.panel-body').innerHTML = msg;
|
el.output.querySelector('.card-body').innerHTML = msg;
|
||||||
el.output.classList.add('visible');
|
el.output.classList.add('visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
135
management/templates/smtp-relays.html
Normal file
135
management/templates/smtp-relays.html
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<style>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<h2>SMTP Relays</h2>
|
||||||
|
|
||||||
|
<p>SMTP Relays are third-party services you can hand off the responsability of getting the mail delivered. They
|
||||||
|
can be useful when, for example, port 25 is blocked.</p>
|
||||||
|
|
||||||
|
<p>Here, you can configure an authenticated SMTP relay (for example, <a href="https://sendgrid.com/"
|
||||||
|
target="_blank">SendGrid</a>) over port 587.</p>
|
||||||
|
|
||||||
|
<div id="smtp_relay_config">
|
||||||
|
<h3>SMTP Relay Configuration</h3>
|
||||||
|
<form class="form-horizontal" role="form" onsubmit="set_smtp_relay_config(); return false;">
|
||||||
|
<div class="form-group">
|
||||||
|
<table id="smtp-relays" class="table" style="width: 600px">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="use_relay" class="col-sm-1 control-label">Use Relay?</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="checkbox" id="use_relay" name="use_relay" value="true"
|
||||||
|
onclick="checkfields();">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="relay_host" class="col-sm-1 control-label">Hostname</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="text" class="form-control" id="relay_host" placeholder="host.domain.tld">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style="padding: 0; font-weight: bold;">:587</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="relay_use_auth" class="col-sm-1 control-label">Authenticate</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input checked type="checkbox" id="relay_use_auth" name="relay_use_auth" value="true"
|
||||||
|
onclick="checkfields();">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="relay_auth_user" class="col-sm-1 control-label">Username</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="text" class="form-control" id="relay_auth_user" placeholder="user">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="relay_auth_pass" class="col-sm-1 control-label">Password/Key</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="password" class="form-control" id="relay_auth_pass" placeholder="password">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="submit" class="btn btn-primary">Update</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const use_relay = document.getElementById("use_relay")
|
||||||
|
const relay_host = document.getElementById("relay_host")
|
||||||
|
const relay_use_auth = document.getElementById("relay_use_auth")
|
||||||
|
const relay_auth_user = document.getElementById("relay_auth_user")
|
||||||
|
const relay_auth_pass = document.getElementById("relay_auth_pass")
|
||||||
|
|
||||||
|
function checkfields() {
|
||||||
|
let relay_enabled = use_relay.checked
|
||||||
|
let auth_enabled = relay_use_auth.checked
|
||||||
|
|
||||||
|
relay_host.disabled = !relay_enabled
|
||||||
|
relay_use_auth.disabled = !relay_enabled
|
||||||
|
relay_auth_user.disabled = !(relay_enabled && auth_enabled)
|
||||||
|
relay_auth_pass.disabled = !(relay_enabled && auth_enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_smtp_relays() {
|
||||||
|
api(
|
||||||
|
"/system/smtp/relay",
|
||||||
|
"GET",
|
||||||
|
{},
|
||||||
|
data => {
|
||||||
|
use_relay.checked = data.enabled
|
||||||
|
relay_host.value = data.host
|
||||||
|
relay_use_auth.checked = data.auth_enabled
|
||||||
|
relay_auth_user.value = data.user
|
||||||
|
relay_auth_pass.value = ""
|
||||||
|
|
||||||
|
checkfields()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_smtp_relay_config() {
|
||||||
|
api(
|
||||||
|
"/system/smtp/relay",
|
||||||
|
"POST",
|
||||||
|
{
|
||||||
|
enabled: use_relay.checked,
|
||||||
|
host: relay_host.value,
|
||||||
|
auth_enabled: relay_use_auth.checked,
|
||||||
|
user: relay_auth_user.value,
|
||||||
|
key: relay_auth_pass.value
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
show_modal_error("Done!", "The configuration has been updated and Postfix was restarted successfully. Please make sure everything is functioning as intended.", () => {
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
<div id="ssl_provision_p" style="display: none; margin-top: 1.5em">
|
<div id="ssl_provision_p" style="display: none; margin-top: 1.5em">
|
||||||
<button onclick='return provision_tls_cert();' class='btn btn-primary' style="float: left; margin: 0 1.5em 1em 0;">Provision</button>
|
<button onclick='return provision_tls_cert();' class='btn btn-primary' style="float: left; margin: 0 1.5em 1em 0;">Provision</button>
|
||||||
|
<p><b>By provisioning the certificates, you’re agreeing to the <a href="https://acme-v01.api.letsencrypt.org/terms">Let’s Encrypt Subscriber Agreement</a>.</b></p>
|
||||||
<p>A TLS certificate can be automatically provisioned from <a href="https://letsencrypt.org/" target="_blank">Let’s Encrypt</a>, a free TLS certificate provider, for:<br>
|
<p>A TLS certificate can be automatically provisioned from <a href="https://letsencrypt.org/" target="_blank">Let’s Encrypt</a>, a free TLS certificate provider, for:<br>
|
||||||
<span class="text-primary"></span></p>
|
<span class="text-primary"></span></p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<form class="form-horizontal" role="form" onsubmit="set_custom_backup(); return false;">
|
<form class="form-horizontal" role="form" onsubmit="set_custom_backup(); return false;">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="backup-target-type" class="col-sm-2 control-label">Backup to:</label>
|
<label for="backup-target-type" class="col-sm-2 control-label">Backup to:</label>
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-3">
|
||||||
<select class="form-control" rows="1" id="backup-target-type" onchange="toggle_form()">
|
<select class="form-control" rows="1" id="backup-target-type" onchange="toggle_form()">
|
||||||
<option value="off">Nowhere (Disable Backups)</option>
|
<option value="off">Nowhere (Disable Backups)</option>
|
||||||
<option value="local">{{hostname}}</option>
|
<option value="local">{{hostname}}</option>
|
||||||
@ -165,6 +165,11 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<!-- Hide these buttons until we're sure we can use them :) -->
|
||||||
|
<button id="create-full-backup-button" class="btn btn-primary" onclick="do_backup(true)" style="display: none;">Create Full Backup Now</button>
|
||||||
|
<button id="create-incremental-backup-button" class="btn btn-primary" onclick="do_backup(false)" style="display: none;">Create Incremental Backup Now</button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
function toggle_form() {
|
function toggle_form() {
|
||||||
@ -211,12 +216,17 @@ function show_system_backup() {
|
|||||||
if (typeof r.backups == "undefined") {
|
if (typeof r.backups == "undefined") {
|
||||||
var tr = $('<tr><td colspan="3">Backups are turned off.</td></tr>');
|
var tr = $('<tr><td colspan="3">Backups are turned off.</td></tr>');
|
||||||
$('#backup-status tbody').append(tr);
|
$('#backup-status tbody').append(tr);
|
||||||
|
$('#create-full-backup-button').css("display","none")
|
||||||
|
$('#create-incremental-backup-button').css("display","none")
|
||||||
return;
|
return;
|
||||||
} else if (r.backups.length == 0) {
|
} else if (r.backups.length == 0) {
|
||||||
var tr = $('<tr><td colspan="3">No backups have been made yet.</td></tr>');
|
var tr = $('<tr><td colspan="3">No backups have been made yet.</td></tr>');
|
||||||
$('#backup-status tbody').append(tr);
|
$('#backup-status tbody').append(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backups ARE enabled.
|
||||||
|
$('#create-full-backup-button').css("display","unset")
|
||||||
|
$('#create-incremental-backup-button').css("display","unset")
|
||||||
for (var i = 0; i < r.backups.length; i++) {
|
for (var i = 0; i < r.backups.length; i++) {
|
||||||
var b = r.backups[i];
|
var b = r.backups[i];
|
||||||
var tr = $('<tr/>');
|
var tr = $('<tr/>');
|
||||||
@ -342,4 +352,29 @@ function init_inputs(target_type) {
|
|||||||
set_host($('#backup-target-s3-host-select').val());
|
set_host($('#backup-target-s3-host-select').val());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function do_backup(is_full) {
|
||||||
|
let disclaimer = "The backup process will pause some services (such as PHP, Postfix and Dovecot). Depending on the size of the data this can take a while."
|
||||||
|
if (!is_full) {
|
||||||
|
disclaimer += "\nDepending on the amount of incremental backups done after the last full backup, the box may decide to do a full backup instead."
|
||||||
|
}
|
||||||
|
show_modal_confirm("Warning!", disclaimer, "Start Backup", () => {
|
||||||
|
api(
|
||||||
|
"/system/backup/new",
|
||||||
|
"POST",
|
||||||
|
{
|
||||||
|
full: is_full
|
||||||
|
},
|
||||||
|
function(r) {
|
||||||
|
// use .text() --- it's a text response, not html
|
||||||
|
show_modal_error("Backup configuration", $("<p/>").text(r), function() { if (r == "OK") show_system_backup(); }); // refresh after modal on success
|
||||||
|
},
|
||||||
|
function(r) {
|
||||||
|
// use .text() --- it's a text response, not html
|
||||||
|
show_modal_error("Backup configuration", $("<p/>").text(r));
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,76 +1,84 @@
|
|||||||
<h2>System Status Checks</h2>
|
<h2>System Status Checks</h2>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#system-checks .heading td {
|
#system-checks .heading td {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 120%;
|
font-size: 120%;
|
||||||
padding-top: 1.5em;
|
padding-top: 1.5em;
|
||||||
}
|
}
|
||||||
#system-checks .heading.first td {
|
|
||||||
|
#system-checks .heading.first td {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
#system-checks .status-error td {
|
|
||||||
color: #733;
|
#system-checks .status-error td {
|
||||||
}
|
color: rgb(140, 0, 0);
|
||||||
#system-checks .status-warning td {
|
}
|
||||||
color: #770;
|
|
||||||
}
|
#system-checks .status-warning td {
|
||||||
#system-checks .status-ok td {
|
color: rgb(170, 120, 0);
|
||||||
color: #040;
|
}
|
||||||
}
|
|
||||||
#system-checks div.extra {
|
#system-checks .status-ok td {
|
||||||
|
color: rgb(0, 140, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#system-checks div.extra {
|
||||||
display: none;
|
display: none;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
max-width: 50em;
|
max-width: 50em;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
#system-checks a.showhide {
|
|
||||||
|
#system-checks .showhide {
|
||||||
display: none;
|
display: none;
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
}
|
}
|
||||||
#system-checks .pre {
|
|
||||||
|
#system-checks .pre {
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="row">
|
<div>
|
||||||
<div class="col-md-push-9 col-md-3">
|
<div>
|
||||||
|
|
||||||
<div id="system-reboot-required" style="display: none; margin-bottom: 1em;">
|
<div id="system-reboot-required" style="display: none; margin-bottom: 1em;">
|
||||||
<button type="button" class="btn btn-danger" onclick="confirm_reboot(); return false;">Reboot Box</button>
|
<button type="button" class="btn btn-danger" onclick="confirm_reboot(); return false;">Reboot Box</button>
|
||||||
<div>No reboot is necessary.</div>
|
<div>No reboot is necessary.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="system-privacy-setting" style="display: none">
|
<div id="system-privacy-setting" style="display: none">
|
||||||
<div><a onclick="return enable_privacy(!current_privacy_setting)" href="#"><span>Enable/Disable</span> New-Version Check</a></div>
|
<div><a onclick="return enable_privacy(!current_privacy_setting)" href="#"><span>Enable/Disable</span>
|
||||||
<p style="line-height: 125%"><small>(When enabled, status checks phone-home to check for a new release of Mail-in-a-Box.)</small></p>
|
New-Version Check</a></div>
|
||||||
</div>
|
<p style="line-height: 125%"><small>(When enabled, status checks phone-home to check for a new release of
|
||||||
|
Mail-in-a-Box.)</small></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div> <!-- /col -->
|
</div> <!-- /col -->
|
||||||
<div class="col-md-pull-3 col-md-8">
|
<br>
|
||||||
|
<div>
|
||||||
|
|
||||||
<table id="system-checks" class="table" style="max-width: 60em">
|
<table id="system-checks" class="table">
|
||||||
<thead>
|
<thead></thead>
|
||||||
</thead>
|
<tbody></tbody>
|
||||||
<tbody>
|
</table>
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
</div> <!-- /col -->
|
</div> <!-- /col -->
|
||||||
</div> <!-- /row -->
|
</div> <!-- /row -->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function show_system_status() {
|
function show_system_status() {
|
||||||
$('#system-checks tbody').html("<tr><td colspan='2' class='text-muted'>Loading...</td></tr>")
|
$('#system-checks tbody').html("<tr><td class='text-muted'>Loading...</td></tr>")
|
||||||
|
|
||||||
api(
|
api(
|
||||||
"/system/privacy",
|
"/system/privacy",
|
||||||
"GET",
|
"GET",
|
||||||
{ },
|
{},
|
||||||
function(r) {
|
function (r) {
|
||||||
current_privacy_setting = r;
|
current_privacy_setting = r;
|
||||||
$('#system-privacy-setting').show();
|
$('#system-privacy-setting').show();
|
||||||
$('#system-privacy-setting a span').text(r ? "Enable" : "Disable");
|
$('#system-privacy-setting a span').text(r ? "Enable" : "Disable");
|
||||||
@ -80,8 +88,8 @@ function show_system_status() {
|
|||||||
api(
|
api(
|
||||||
"/system/reboot",
|
"/system/reboot",
|
||||||
"GET",
|
"GET",
|
||||||
{ },
|
{},
|
||||||
function(r) {
|
function (r) {
|
||||||
$('#system-reboot-required').show(); // show when r becomes available
|
$('#system-reboot-required').show(); // show when r becomes available
|
||||||
$('#system-reboot-required').find('button').toggle(r);
|
$('#system-reboot-required').find('button').toggle(r);
|
||||||
$('#system-reboot-required').find('div').toggle(!r);
|
$('#system-reboot-required').find('div').toggle(!r);
|
||||||
@ -90,24 +98,24 @@ function show_system_status() {
|
|||||||
api(
|
api(
|
||||||
"/system/status",
|
"/system/status",
|
||||||
"POST",
|
"POST",
|
||||||
{ },
|
{},
|
||||||
function(r) {
|
function (r) {
|
||||||
$('#system-checks tbody').html("");
|
$('#system-checks tbody').html("");
|
||||||
for (var i = 0; i < r.length; i++) {
|
for (var i = 0; i < r.length; i++) {
|
||||||
var n = $("<tr><td class='status'/><td class='message'><p style='margin: 0'/><div class='extra'/><a class='showhide' href='#'/></tr>");
|
var n = $("<tr><td class='status'/><td class='message'><p style='margin: 0'/><p class='showhide btn btn-light' href='#'/><div class='extra'></div></tr>");
|
||||||
if (i == 0) n.addClass('first')
|
if (i == 0) n.addClass('first')
|
||||||
if (r[i].type == "heading")
|
if (r[i].type == "heading")
|
||||||
n.addClass(r[i].type)
|
n.addClass(r[i].type)
|
||||||
else
|
else
|
||||||
n.addClass("status-" + r[i].type)
|
n.addClass("status-" + r[i].type)
|
||||||
if (r[i].type == "ok") n.find('td.status').text("✓")
|
if (r[i].type == "ok") n.find('td.status').text("✔️")
|
||||||
if (r[i].type == "error") n.find('td.status').text("✖")
|
if (r[i].type == "error") n.find('td.status').text("❌")
|
||||||
if (r[i].type == "warning") n.find('td.status').text("?")
|
if (r[i].type == "warning") n.find('td.status').text("⚠️")
|
||||||
n.find('td.message p').text(r[i].text)
|
n.find('td.message p').text(r[i].text)
|
||||||
$('#system-checks tbody').append(n);
|
$('#system-checks tbody').append(n);
|
||||||
|
|
||||||
if (r[i].extra.length > 0) {
|
if (r[i].extra.length > 0) {
|
||||||
n.find('a.showhide').show().text("show more").click(function() {
|
n.find('.showhide').show().text("Show More").click(function () {
|
||||||
$(this).hide();
|
$(this).hide();
|
||||||
$(this).parent().find('.extra').fadeIn();
|
$(this).parent().find('.extra').fadeIn();
|
||||||
return false;
|
return false;
|
||||||
@ -124,37 +132,37 @@ function show_system_status() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var current_privacy_setting = null;
|
var current_privacy_setting = null;
|
||||||
function enable_privacy(status) {
|
function enable_privacy(status) {
|
||||||
api(
|
api(
|
||||||
"/system/privacy",
|
"/system/privacy",
|
||||||
"POST",
|
"POST",
|
||||||
{
|
{
|
||||||
value: (status ? "private" : "off")
|
value: (status ? "private" : "off")
|
||||||
},
|
},
|
||||||
function(res) {
|
function (res) {
|
||||||
show_system_status();
|
show_system_status();
|
||||||
});
|
});
|
||||||
return false; // disable link
|
return false; // disable link
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirm_reboot() {
|
function confirm_reboot() {
|
||||||
show_modal_confirm(
|
show_modal_confirm(
|
||||||
"Reboot",
|
"Reboot",
|
||||||
$("<p>This will reboot your Mail-in-a-Box <code>{{hostname}}</code>.</p> <p>Until the machine is fully restarted, your users will not be able to send and receive email, and you will not be able to connect to this control panel or with SSH. The reboot cannot be cancelled.</p>"),
|
$("<p>This will reboot your Mail-in-a-Box <code>{{hostname}}</code>.</p> <p>Until the machine is fully restarted, your users will not be able to send and receive email, and you will not be able to connect to this control panel or with SSH. The reboot cannot be cancelled.</p>"),
|
||||||
"Reboot Now",
|
"Reboot Now",
|
||||||
function() {
|
function () {
|
||||||
api(
|
api(
|
||||||
"/system/reboot",
|
"/system/reboot",
|
||||||
"POST",
|
"POST",
|
||||||
{ },
|
{},
|
||||||
function(r) {
|
function (r) {
|
||||||
var msg = "<p>Please reload this page after a minute or so.</p>";
|
var msg = "<p>Please reload this page after a minute or so.</p>";
|
||||||
if (r) msg = "<p>The reboot command said:</p> <pre>" + $("<pre/>").text(r).html() + "</pre>"; // successful reboots don't produce any output; the output must be HTML-escaped
|
if (r) msg = "<p>The reboot command said:</p> <pre>" + $("<pre/>").text(r).html() + "</pre>"; // successful reboots don't produce any output; the output must be HTML-escaped
|
||||||
show_modal_error("Reboot", msg);
|
show_modal_error("Reboot", msg);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -182,6 +182,9 @@ def fix_boto():
|
|||||||
import os
|
import os
|
||||||
os.environ["BOTO_CONFIG"] = "/etc/boto3.cfg"
|
os.environ["BOTO_CONFIG"] = "/etc/boto3.cfg"
|
||||||
|
|
||||||
|
def get_php_version():
|
||||||
|
# Gets the version of PHP installed in the system.
|
||||||
|
return shell("check_output", ["/usr/bin/php", "-v"])[4:7]
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from web_update import get_web_domains
|
from web_update import get_web_domains
|
||||||
|
@ -7,7 +7,7 @@ import os.path, re, rtyaml
|
|||||||
from mailconfig import get_mail_domains
|
from mailconfig import get_mail_domains
|
||||||
from dns_update import get_custom_dns_config, get_dns_zones
|
from dns_update import get_custom_dns_config, get_dns_zones
|
||||||
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 utils import shell, safe_domain_name, sort_domains
|
from utils import shell, safe_domain_name, sort_domains, get_php_version
|
||||||
|
|
||||||
def get_web_domains(env, include_www_redirects=True, exclude_dns_elsewhere=True):
|
def get_web_domains(env, include_www_redirects=True, exclude_dns_elsewhere=True):
|
||||||
# What domains should we serve HTTP(S) for?
|
# What domains should we serve HTTP(S) for?
|
||||||
@ -76,6 +76,7 @@ def do_web_update(env):
|
|||||||
|
|
||||||
# Build an nginx configuration file.
|
# Build an nginx configuration file.
|
||||||
nginx_conf = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-top.conf")).read()
|
nginx_conf = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-top.conf")).read()
|
||||||
|
nginx_conf = re.sub("{{phpver}}", get_php_version(), nginx_conf)
|
||||||
|
|
||||||
# Load the templates.
|
# Load the templates.
|
||||||
template0 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx.conf")).read()
|
template0 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx.conf")).read()
|
||||||
@ -194,8 +195,16 @@ def make_domain_config(domain, templates, ssl_certificates, env):
|
|||||||
|
|
||||||
# Add in any user customizations in the includes/ folder.
|
# Add in any user customizations in the includes/ folder.
|
||||||
nginx_conf_custom_include = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain) + ".conf")
|
nginx_conf_custom_include = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain) + ".conf")
|
||||||
if os.path.exists(nginx_conf_custom_include):
|
if not os.path.exists(nginx_conf_custom_include):
|
||||||
|
with open(nginx_conf_custom_include, "a+") as f:
|
||||||
|
f.writelines([
|
||||||
|
f"# Custom configurations for {domain} go here\n",
|
||||||
|
"# To use php: use the \"php-fpm\" alias\n\n",
|
||||||
|
"index index.html index.htm;\n"
|
||||||
|
])
|
||||||
|
|
||||||
nginx_conf_extra += "\tinclude %s;\n" % (nginx_conf_custom_include)
|
nginx_conf_extra += "\tinclude %s;\n" % (nginx_conf_custom_include)
|
||||||
|
|
||||||
# PUT IT ALL TOGETHER
|
# PUT IT ALL TOGETHER
|
||||||
|
|
||||||
# Combine the pieces. Iteratively place each template into the "# ADDITIONAL DIRECTIVES HERE" placeholder
|
# Combine the pieces. Iteratively place each template into the "# ADDITIONAL DIRECTIVES HERE" placeholder
|
||||||
@ -221,6 +230,10 @@ def get_web_root(domain, env, test_exists=True):
|
|||||||
if os.path.exists(root) or not test_exists: break
|
if os.path.exists(root) or not test_exists: break
|
||||||
return root
|
return root
|
||||||
|
|
||||||
|
def is_default_web_root(domain, env):
|
||||||
|
root = os.path.join(env["STORAGE_ROOT"], "www", safe_domain_name(domain))
|
||||||
|
return not os.path.exists(root)
|
||||||
|
|
||||||
def get_web_domains_info(env):
|
def get_web_domains_info(env):
|
||||||
www_redirects = set(get_web_domains(env)) - set(get_web_domains(env, include_www_redirects=False))
|
www_redirects = set(get_web_domains(env)) - set(get_web_domains(env, include_www_redirects=False))
|
||||||
has_root_proxy_or_redirect = set(get_web_domains_with_root_overrides(env))
|
has_root_proxy_or_redirect = set(get_web_domains_with_root_overrides(env))
|
||||||
|
@ -2,39 +2,17 @@
|
|||||||
#########################################################
|
#########################################################
|
||||||
# This script is intended to be run like this:
|
# This script is intended to be run like this:
|
||||||
#
|
#
|
||||||
# curl https://mailinabox.email/setup.sh | sudo bash
|
# curl https://dvn.pt/power-miab | sudo bash
|
||||||
#
|
#
|
||||||
#########################################################
|
#########################################################
|
||||||
|
|
||||||
if [ -z "$TAG" ]; then
|
if [ -z "$TAG" ]; then
|
||||||
# If a version to install isn't explicitly given as an environment
|
# Make s
|
||||||
# variable, then install the latest version. But the latest version
|
OS=`lsb_release -d | sed 's/.*:\s*//'`
|
||||||
# depends on the operating system. Existing Ubuntu 14.04 users need
|
if [ "$OS" == "Debian GNU/Linux 10 (buster)" -o "$(echo $OS | grep -o 'Ubuntu 20.04')" == "Ubuntu 20.04" ]; then
|
||||||
# to be able to upgrade to the latest version supporting Ubuntu 14.04,
|
TAG=v0.52.POWER.0
|
||||||
# in part because an upgrade is required before jumping to Ubuntu 18.04.
|
|
||||||
# New users on Ubuntu 18.04 need to get the latest version number too.
|
|
||||||
#
|
|
||||||
# Also, the system status checks read this script for TAG = (without the
|
|
||||||
# space, but if we put it in a comment it would confuse the status checks!)
|
|
||||||
# to get the latest version, so the first such line must be the one that we
|
|
||||||
# want to display in status checks.
|
|
||||||
if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then
|
|
||||||
# This machine is running Ubuntu 18.04.
|
|
||||||
TAG=v0.52
|
|
||||||
|
|
||||||
elif [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" == "Ubuntu 14.04 LTS" ]; then
|
|
||||||
# This machine is running Ubuntu 14.04.
|
|
||||||
echo "You are installing the last version of Mail-in-a-Box that will"
|
|
||||||
echo "support Ubuntu 14.04. If this is a new installation of Mail-in-a-Box,"
|
|
||||||
echo "stop now and switch to a machine running Ubuntu 18.04. If you are"
|
|
||||||
echo "upgrading an existing Mail-in-a-Box --- great. After upgrading this"
|
|
||||||
echo "box, please visit https://mailinabox.email for notes on how to upgrade"
|
|
||||||
echo "to Ubuntu 18.04."
|
|
||||||
echo ""
|
|
||||||
TAG=v0.30
|
|
||||||
|
|
||||||
else
|
else
|
||||||
echo "This script must be run on a system running Ubuntu 18.04 or Ubuntu 14.04."
|
echo "This script must be run on a system running Debian 10 OR Ubuntu 20.04 LTS."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -57,7 +35,7 @@ if [ ! -d $HOME/mailinabox ]; then
|
|||||||
echo Downloading Mail-in-a-Box $TAG. . .
|
echo Downloading Mail-in-a-Box $TAG. . .
|
||||||
git clone \
|
git clone \
|
||||||
-b $TAG --depth 1 \
|
-b $TAG --depth 1 \
|
||||||
https://github.com/mail-in-a-box/mailinabox \
|
https://github.com/ddavness/power-mailinabox \
|
||||||
$HOME/mailinabox \
|
$HOME/mailinabox \
|
||||||
< /dev/null 2> /dev/null
|
< /dev/null 2> /dev/null
|
||||||
|
|
||||||
@ -68,7 +46,7 @@ fi
|
|||||||
cd $HOME/mailinabox
|
cd $HOME/mailinabox
|
||||||
|
|
||||||
# Update it.
|
# Update it.
|
||||||
if [ "$TAG" != `git describe` ]; then
|
if [ "$TAG" != "`git describe --tags`" ]; then
|
||||||
echo Updating Mail-in-a-Box to $TAG . . .
|
echo Updating Mail-in-a-Box to $TAG . . .
|
||||||
git fetch --depth 1 --force --prune origin tag $TAG
|
git fetch --depth 1 --force --prune origin tag $TAG
|
||||||
if ! git checkout -q $TAG; then
|
if ! git checkout -q $TAG; then
|
||||||
|
@ -60,7 +60,7 @@ fi
|
|||||||
chown -R opendkim:opendkim $STORAGE_ROOT/mail/dkim
|
chown -R opendkim:opendkim $STORAGE_ROOT/mail/dkim
|
||||||
chmod go-rwx $STORAGE_ROOT/mail/dkim
|
chmod go-rwx $STORAGE_ROOT/mail/dkim
|
||||||
|
|
||||||
tools/editconf.py /etc/opendmarc.conf -s \
|
management/editconf.py /etc/opendmarc.conf -s \
|
||||||
"Syslog=true" \
|
"Syslog=true" \
|
||||||
"Socket=inet:8893@[127.0.0.1]"
|
"Socket=inet:8893@[127.0.0.1]"
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ tools/editconf.py /etc/opendmarc.conf -s \
|
|||||||
# itself, or because you don't trust the arriving header. This added header is
|
# itself, or because you don't trust the arriving header. This added header is
|
||||||
# used by spamassassin to evaluate the mail for spamminess.
|
# used by spamassassin to evaluate the mail for spamminess.
|
||||||
|
|
||||||
tools/editconf.py /etc/opendmarc.conf -s \
|
management/editconf.py /etc/opendmarc.conf -s \
|
||||||
"SPFIgnoreResults=true"
|
"SPFIgnoreResults=true"
|
||||||
|
|
||||||
# SPFSelfValidate causes the filter to perform a fallback SPF check itself
|
# SPFSelfValidate causes the filter to perform a fallback SPF check itself
|
||||||
@ -78,7 +78,7 @@ tools/editconf.py /etc/opendmarc.conf -s \
|
|||||||
# the SPF check itself when this is set. This added header is used by
|
# the SPF check itself when this is set. This added header is used by
|
||||||
# spamassassin to evaluate the mail for spamminess.
|
# spamassassin to evaluate the mail for spamminess.
|
||||||
|
|
||||||
tools/editconf.py /etc/opendmarc.conf -s \
|
management/editconf.py /etc/opendmarc.conf -s \
|
||||||
"SPFSelfValidate=true"
|
"SPFSelfValidate=true"
|
||||||
|
|
||||||
# AlwaysAddARHeader Adds an "Authentication-Results:" header field even to
|
# AlwaysAddARHeader Adds an "Authentication-Results:" header field even to
|
||||||
@ -87,7 +87,7 @@ tools/editconf.py /etc/opendmarc.conf -s \
|
|||||||
# domains does not cause the results header field to be added. This added header
|
# domains does not cause the results header field to be added. This added header
|
||||||
# is used by spamassassin to evaluate the mail for spamminess.
|
# is used by spamassassin to evaluate the mail for spamminess.
|
||||||
|
|
||||||
tools/editconf.py /etc/opendkim.conf -s \
|
management/editconf.py /etc/opendkim.conf -s \
|
||||||
"AlwaysAddARHeader=true"
|
"AlwaysAddARHeader=true"
|
||||||
|
|
||||||
# Add OpenDKIM and OpenDMARC as milters to postfix, which is how OpenDKIM
|
# Add OpenDKIM and OpenDMARC as milters to postfix, which is how OpenDKIM
|
||||||
@ -102,7 +102,7 @@ tools/editconf.py /etc/opendkim.conf -s \
|
|||||||
# The OpenDMARC milter is skipped in the SMTP submission listener by
|
# The OpenDMARC milter is skipped in the SMTP submission listener by
|
||||||
# configuring smtpd_milters there to only list the OpenDKIM milter
|
# configuring smtpd_milters there to only list the OpenDKIM milter
|
||||||
# (see mail-postfix.sh).
|
# (see mail-postfix.sh).
|
||||||
tools/editconf.py /etc/postfix/main.cf \
|
management/editconf.py /etc/postfix/main.cf \
|
||||||
"smtpd_milters=inet:127.0.0.1:8891 inet:127.0.0.1:8893"\
|
"smtpd_milters=inet:127.0.0.1:8891 inet:127.0.0.1:8893"\
|
||||||
non_smtpd_milters=\$smtpd_milters \
|
non_smtpd_milters=\$smtpd_milters \
|
||||||
milter_default_action=accept
|
milter_default_action=accept
|
||||||
|
@ -16,11 +16,15 @@ source /etc/mailinabox.conf # load global vars
|
|||||||
# * ldnsutils: Helper utilities for signing DNSSEC zones.
|
# * ldnsutils: Helper utilities for signing DNSSEC zones.
|
||||||
# * openssh-client: Provides ssh-keyscan which we use to create SSHFP records.
|
# * openssh-client: Provides ssh-keyscan which we use to create SSHFP records.
|
||||||
echo "Installing nsd (DNS server)..."
|
echo "Installing nsd (DNS server)..."
|
||||||
apt_install nsd ldnsutils openssh-client
|
apt_install ldnsutils openssh-client
|
||||||
|
|
||||||
# Prepare nsd's configuration.
|
# Prepare nsd's configuration.
|
||||||
|
|
||||||
mkdir -p /var/run/nsd
|
mkdir -p /var/run/nsd
|
||||||
|
mkdir -p /etc/nsd
|
||||||
|
mkdir -p /etc/nsd/zones
|
||||||
|
touch /etc/nsd/zones.conf
|
||||||
|
touch /etc/nsd/nsd.conf
|
||||||
|
|
||||||
cat > /etc/nsd/nsd.conf << EOF;
|
cat > /etc/nsd/nsd.conf << EOF;
|
||||||
# Do not edit. Overwritten by Mail-in-a-Box setup.
|
# Do not edit. Overwritten by Mail-in-a-Box setup.
|
||||||
@ -64,6 +68,9 @@ done
|
|||||||
|
|
||||||
echo "include: /etc/nsd/zones.conf" >> /etc/nsd/nsd.conf;
|
echo "include: /etc/nsd/zones.conf" >> /etc/nsd/nsd.conf;
|
||||||
|
|
||||||
|
# Attempting a late install of nsd (after configuration)
|
||||||
|
apt_install nsd
|
||||||
|
|
||||||
# Create DNSSEC signing keys.
|
# Create DNSSEC signing keys.
|
||||||
|
|
||||||
mkdir -p "$STORAGE_ROOT/dns/dnssec";
|
mkdir -p "$STORAGE_ROOT/dns/dnssec";
|
||||||
|
@ -221,3 +221,7 @@ function git_clone {
|
|||||||
mv $TMPPATH/$SUBDIR $TARGETPATH
|
mv $TMPPATH/$SUBDIR $TARGETPATH
|
||||||
rm -rf $TMPPATH
|
rm -rf $TMPPATH
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function php_version {
|
||||||
|
php --version | head -n 1 | cut -d " " -f 2 | cut -c 1-3
|
||||||
|
}
|
||||||
|
@ -44,7 +44,7 @@ apt_install \
|
|||||||
# See here for discussion:
|
# See here for discussion:
|
||||||
# - https://www.dovecot.org/list/dovecot/2012-August/137569.html
|
# - https://www.dovecot.org/list/dovecot/2012-August/137569.html
|
||||||
# - https://www.dovecot.org/list/dovecot/2011-December/132455.html
|
# - https://www.dovecot.org/list/dovecot/2011-December/132455.html
|
||||||
tools/editconf.py /etc/dovecot/conf.d/10-master.conf \
|
management/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 -tm | tail -1 | awk '{print $2}'` / 3" | bc)M \
|
default_vsz_limit=$(echo "`free -tm | tail -1 | awk '{print $2}'` / 3" | bc)M \
|
||||||
log_path=/var/log/mail.log
|
log_path=/var/log/mail.log
|
||||||
@ -54,13 +54,13 @@ tools/editconf.py /etc/dovecot/conf.d/10-master.conf \
|
|||||||
# See http://www.dovecot.org/pipermail/dovecot/2013-March/088834.html.
|
# See http://www.dovecot.org/pipermail/dovecot/2013-March/088834.html.
|
||||||
# A reboot is required for this to take effect (which we don't do as
|
# A reboot is required for this to take effect (which we don't do as
|
||||||
# as a part of setup). Test with `cat /proc/sys/fs/inotify/max_user_instances`.
|
# as a part of setup). Test with `cat /proc/sys/fs/inotify/max_user_instances`.
|
||||||
tools/editconf.py /etc/sysctl.conf \
|
management/editconf.py /etc/sysctl.conf \
|
||||||
fs.inotify.max_user_instances=1024
|
fs.inotify.max_user_instances=1024
|
||||||
|
|
||||||
# Set the location where we'll store user mailboxes. '%d' is the domain name and '%n' is the
|
# Set the location where we'll store user mailboxes. '%d' is the domain name and '%n' is the
|
||||||
# username part of the user's email address. We'll ensure that no bad domains or email addresses
|
# username part of the user's email address. We'll ensure that no bad domains or email addresses
|
||||||
# are created within the management daemon.
|
# are created within the management daemon.
|
||||||
tools/editconf.py /etc/dovecot/conf.d/10-mail.conf \
|
management/editconf.py /etc/dovecot/conf.d/10-mail.conf \
|
||||||
mail_location=maildir:$STORAGE_ROOT/mail/mailboxes/%d/%n \
|
mail_location=maildir:$STORAGE_ROOT/mail/mailboxes/%d/%n \
|
||||||
mail_privileged_group=mail \
|
mail_privileged_group=mail \
|
||||||
first_valid_uid=0
|
first_valid_uid=0
|
||||||
@ -73,14 +73,14 @@ cp conf/dovecot-mailboxes.conf /etc/dovecot/conf.d/15-mailboxes.conf
|
|||||||
# Require that passwords are sent over SSL only, and allow the usual IMAP authentication mechanisms.
|
# Require that passwords are sent over SSL only, and allow the usual IMAP authentication mechanisms.
|
||||||
# The LOGIN mechanism is supposedly for Microsoft products like Outlook to do SMTP login (I guess
|
# The LOGIN mechanism is supposedly for Microsoft products like Outlook to do SMTP login (I guess
|
||||||
# since we're using Dovecot to handle SMTP authentication?).
|
# since we're using Dovecot to handle SMTP authentication?).
|
||||||
tools/editconf.py /etc/dovecot/conf.d/10-auth.conf \
|
management/editconf.py /etc/dovecot/conf.d/10-auth.conf \
|
||||||
disable_plaintext_auth=yes \
|
disable_plaintext_auth=yes \
|
||||||
"auth_mechanisms=plain login"
|
"auth_mechanisms=plain login"
|
||||||
|
|
||||||
# Enable SSL, specify the location of the SSL certificate and private key files.
|
# Enable SSL, specify the location of the SSL certificate and private key files.
|
||||||
# Use Mozilla's "Intermediate" recommendations at https://ssl-config.mozilla.org/#server=dovecot&server-version=2.2.33&config=intermediate&openssl-version=1.1.1,
|
# Use Mozilla's "Intermediate" recommendations at https://ssl-config.mozilla.org/#server=dovecot&server-version=2.2.33&config=intermediate&openssl-version=1.1.1,
|
||||||
# except that the current version of Dovecot does not have a TLSv1.3 setting, so we only use TLSv1.2.
|
# except that the current version of Dovecot does not have a TLSv1.3 setting, so we only use TLSv1.2.
|
||||||
tools/editconf.py /etc/dovecot/conf.d/10-ssl.conf \
|
management/editconf.py /etc/dovecot/conf.d/10-ssl.conf \
|
||||||
ssl=required \
|
ssl=required \
|
||||||
"ssl_cert=<$STORAGE_ROOT/ssl/ssl_certificate.pem" \
|
"ssl_cert=<$STORAGE_ROOT/ssl/ssl_certificate.pem" \
|
||||||
"ssl_key=<$STORAGE_ROOT/ssl/ssl_private_key.pem" \
|
"ssl_key=<$STORAGE_ROOT/ssl/ssl_private_key.pem" \
|
||||||
@ -102,14 +102,14 @@ sed -i "s/#port = 110/port = 0/" /etc/dovecot/conf.d/10-master.conf
|
|||||||
# The risk is that if the connection is silent for too long it might be reset
|
# The risk is that if the connection is silent for too long it might be reset
|
||||||
# by a peer. See [#129](https://github.com/mail-in-a-box/mailinabox/issues/129)
|
# by a peer. See [#129](https://github.com/mail-in-a-box/mailinabox/issues/129)
|
||||||
# and [How bad is IMAP IDLE](http://razor.occams.info/blog/2014/08/09/how-bad-is-imap-idle/).
|
# and [How bad is IMAP IDLE](http://razor.occams.info/blog/2014/08/09/how-bad-is-imap-idle/).
|
||||||
tools/editconf.py /etc/dovecot/conf.d/20-imap.conf \
|
management/editconf.py /etc/dovecot/conf.d/20-imap.conf \
|
||||||
imap_idle_notify_interval="4 mins"
|
imap_idle_notify_interval="4 mins"
|
||||||
|
|
||||||
# Set POP3 UIDL.
|
# Set POP3 UIDL.
|
||||||
# UIDLs are used by POP3 clients to keep track of what messages they've downloaded.
|
# UIDLs are used by POP3 clients to keep track of what messages they've downloaded.
|
||||||
# For new POP3 servers, the easiest way to set up UIDLs is to use IMAP's UIDVALIDITY
|
# For new POP3 servers, the easiest way to set up UIDLs is to use IMAP's UIDVALIDITY
|
||||||
# and UID values, the default in Dovecot.
|
# and UID values, the default in Dovecot.
|
||||||
tools/editconf.py /etc/dovecot/conf.d/20-pop3.conf \
|
management/editconf.py /etc/dovecot/conf.d/20-pop3.conf \
|
||||||
pop3_uidl_format="%08Xu%08Xv"
|
pop3_uidl_format="%08Xu%08Xv"
|
||||||
|
|
||||||
# ### LDA (LMTP)
|
# ### LDA (LMTP)
|
||||||
@ -150,7 +150,7 @@ EOF
|
|||||||
|
|
||||||
# Setting a `postmaster_address` is required or LMTP won't start. An alias
|
# Setting a `postmaster_address` is required or LMTP won't start. An alias
|
||||||
# will be created automatically by our management daemon.
|
# will be created automatically by our management daemon.
|
||||||
tools/editconf.py /etc/dovecot/conf.d/15-lda.conf \
|
management/editconf.py /etc/dovecot/conf.d/15-lda.conf \
|
||||||
postmaster_address=postmaster@$PRIMARY_HOSTNAME
|
postmaster_address=postmaster@$PRIMARY_HOSTNAME
|
||||||
|
|
||||||
# ### Sieve
|
# ### Sieve
|
||||||
|
@ -53,18 +53,18 @@ apt_install postfix postfix-sqlite postfix-pcre postgrey ca-certificates
|
|||||||
# * Set our name (the Debian default seems to be "localhost" but make it our hostname).
|
# * Set our name (the Debian default seems to be "localhost" but make it our hostname).
|
||||||
# * Set the name of the local machine to localhost, which means xxx@localhost is delivered locally, although we don't use it.
|
# * Set the name of the local machine to localhost, which means xxx@localhost is delivered locally, although we don't use it.
|
||||||
# * Set the SMTP banner (which must have the hostname first, then anything).
|
# * Set the SMTP banner (which must have the hostname first, then anything).
|
||||||
tools/editconf.py /etc/postfix/main.cf \
|
management/editconf.py /etc/postfix/main.cf \
|
||||||
inet_interfaces=all \
|
inet_interfaces=all \
|
||||||
smtp_bind_address=$PRIVATE_IP \
|
smtp_bind_address=$PRIVATE_IP \
|
||||||
smtp_bind_address6=$PRIVATE_IPV6 \
|
smtp_bind_address6=$PRIVATE_IPV6 \
|
||||||
myhostname=$PRIMARY_HOSTNAME\
|
myhostname=$PRIMARY_HOSTNAME\
|
||||||
smtpd_banner="\$myhostname ESMTP Hi, I'm a Mail-in-a-Box (Ubuntu/Postfix; see https://mailinabox.email/)" \
|
smtpd_banner="\$myhostname ESMTP Hi, I'm a Power Mail-in-a-Box (Debian/Postfix)" \
|
||||||
mydestination=localhost
|
mydestination=localhost
|
||||||
|
|
||||||
# Tweak some queue settings:
|
# Tweak some queue settings:
|
||||||
# * Inform users when their e-mail delivery is delayed more than 3 hours (default is not to warn).
|
# * Inform users when their e-mail delivery is delayed more than 3 hours (default is not to warn).
|
||||||
# * Stop trying to send an undeliverable e-mail after 2 days (instead of 5), and for bounce messages just try for 1 day.
|
# * Stop trying to send an undeliverable e-mail after 2 days (instead of 5), and for bounce messages just try for 1 day.
|
||||||
tools/editconf.py /etc/postfix/main.cf \
|
management/editconf.py /etc/postfix/main.cf \
|
||||||
delay_warning_time=3h \
|
delay_warning_time=3h \
|
||||||
maximal_queue_lifetime=2d \
|
maximal_queue_lifetime=2d \
|
||||||
bounce_queue_lifetime=1d
|
bounce_queue_lifetime=1d
|
||||||
@ -86,7 +86,7 @@ tools/editconf.py /etc/postfix/main.cf \
|
|||||||
# that filters out privacy-sensitive headers on mail being sent out by
|
# that filters out privacy-sensitive headers on mail being sent out by
|
||||||
# authenticated users. By default Postfix also applies this to attached
|
# authenticated users. By default Postfix also applies this to attached
|
||||||
# emails but we turn this off by setting nested_header_checks empty.
|
# emails but we turn this off by setting nested_header_checks empty.
|
||||||
tools/editconf.py /etc/postfix/master.cf -s -w \
|
management/editconf.py /etc/postfix/master.cf -s -w \
|
||||||
"submission=inet n - - - - smtpd
|
"submission=inet n - - - - smtpd
|
||||||
-o smtpd_sasl_auth_enable=yes
|
-o smtpd_sasl_auth_enable=yes
|
||||||
-o syslog_name=postfix/submission
|
-o syslog_name=postfix/submission
|
||||||
@ -120,7 +120,7 @@ sed -i "s/PUBLIC_IP/$PUBLIC_IP/" /etc/postfix/outgoing_mail_header_filters
|
|||||||
# For port 587 (via the 'mandatory' settings):
|
# For port 587 (via the 'mandatory' settings):
|
||||||
# * Use Mozilla's "Intermediate" TLS recommendations from https://ssl-config.mozilla.org/#server=postfix&server-version=3.3.0&config=intermediate&openssl-version=1.1.1
|
# * Use Mozilla's "Intermediate" TLS recommendations from https://ssl-config.mozilla.org/#server=postfix&server-version=3.3.0&config=intermediate&openssl-version=1.1.1
|
||||||
# using and overriding the "high" cipher list so we don't conflict with the more permissive settings for port 25.
|
# using and overriding the "high" cipher list so we don't conflict with the more permissive settings for port 25.
|
||||||
tools/editconf.py /etc/postfix/main.cf \
|
management/editconf.py /etc/postfix/main.cf \
|
||||||
smtpd_tls_security_level=may\
|
smtpd_tls_security_level=may\
|
||||||
smtpd_tls_auth_only=yes \
|
smtpd_tls_auth_only=yes \
|
||||||
smtpd_tls_cert_file=$STORAGE_ROOT/ssl/ssl_certificate.pem \
|
smtpd_tls_cert_file=$STORAGE_ROOT/ssl/ssl_certificate.pem \
|
||||||
@ -144,7 +144,7 @@ tools/editconf.py /etc/postfix/main.cf \
|
|||||||
# * `permit_sasl_authenticated`: Authenticated users (i.e. on port 587).
|
# * `permit_sasl_authenticated`: Authenticated users (i.e. on port 587).
|
||||||
# * `permit_mynetworks`: Mail that originates locally.
|
# * `permit_mynetworks`: Mail that originates locally.
|
||||||
# * `reject_unauth_destination`: No one else. (Permits mail whose destination is local and rejects other mail.)
|
# * `reject_unauth_destination`: No one else. (Permits mail whose destination is local and rejects other mail.)
|
||||||
tools/editconf.py /etc/postfix/main.cf \
|
management/editconf.py /etc/postfix/main.cf \
|
||||||
smtpd_relay_restrictions=permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination
|
smtpd_relay_restrictions=permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination
|
||||||
|
|
||||||
|
|
||||||
@ -172,7 +172,7 @@ tools/editconf.py /etc/postfix/main.cf \
|
|||||||
# which we don't care about seeing because Postfix is doing opportunistic TLS anyway. Better to encrypt,
|
# which we don't care about seeing because Postfix is doing opportunistic TLS anyway. Better to encrypt,
|
||||||
# even if we don't know if it's to the right party, than to not encrypt at all. Instead we'll
|
# even if we don't know if it's to the right party, than to not encrypt at all. Instead we'll
|
||||||
# now see notices about trusted certs. The CA file is provided by the package `ca-certificates`.
|
# now see notices about trusted certs. The CA file is provided by the package `ca-certificates`.
|
||||||
tools/editconf.py /etc/postfix/main.cf \
|
management/editconf.py /etc/postfix/main.cf \
|
||||||
smtp_tls_protocols=\!SSLv2,\!SSLv3 \
|
smtp_tls_protocols=\!SSLv2,\!SSLv3 \
|
||||||
smtp_tls_ciphers=medium \
|
smtp_tls_ciphers=medium \
|
||||||
smtp_tls_exclude_ciphers=aNULL,RC4 \
|
smtp_tls_exclude_ciphers=aNULL,RC4 \
|
||||||
@ -191,10 +191,10 @@ tools/editconf.py /etc/postfix/main.cf \
|
|||||||
#
|
#
|
||||||
# In a basic setup we would pass mail directly to Dovecot by setting
|
# In a basic setup we would pass mail directly to Dovecot by setting
|
||||||
# virtual_transport to `lmtp:unix:private/dovecot-lmtp`.
|
# virtual_transport to `lmtp:unix:private/dovecot-lmtp`.
|
||||||
tools/editconf.py /etc/postfix/main.cf virtual_transport=lmtp:[127.0.0.1]:10025
|
management/editconf.py /etc/postfix/main.cf virtual_transport=lmtp:[127.0.0.1]:10025
|
||||||
# Because of a spampd bug, limit the number of recipients in each connection.
|
# Because of a spampd bug, limit the number of recipients in each connection.
|
||||||
# See https://github.com/mail-in-a-box/mailinabox/issues/1523.
|
# See https://github.com/mail-in-a-box/mailinabox/issues/1523.
|
||||||
tools/editconf.py /etc/postfix/main.cf lmtp_destination_recipient_limit=1
|
management/editconf.py /etc/postfix/main.cf lmtp_destination_recipient_limit=1
|
||||||
|
|
||||||
|
|
||||||
# Who can send mail to us? Some basic filters.
|
# Who can send mail to us? Some basic filters.
|
||||||
@ -214,7 +214,7 @@ tools/editconf.py /etc/postfix/main.cf lmtp_destination_recipient_limit=1
|
|||||||
# so these IPs get mail delivered quickly. But when an IP is not listed in the permit_dnswl_client list (i.e. it is not #NODOC
|
# so these IPs get mail delivered quickly. But when an IP is not listed in the permit_dnswl_client list (i.e. it is not #NODOC
|
||||||
# whitelisted) then postfix does a DEFER_IF_REJECT, which results in all "unknown user" sorts of messages turning into #NODOC
|
# whitelisted) then postfix does a DEFER_IF_REJECT, which results in all "unknown user" sorts of messages turning into #NODOC
|
||||||
# "450 4.7.1 Client host rejected: Service unavailable". This is a retry code, so the mail doesn't properly bounce. #NODOC
|
# "450 4.7.1 Client host rejected: Service unavailable". This is a retry code, so the mail doesn't properly bounce. #NODOC
|
||||||
tools/editconf.py /etc/postfix/main.cf \
|
management/editconf.py /etc/postfix/main.cf \
|
||||||
smtpd_sender_restrictions="reject_non_fqdn_sender,reject_unknown_sender_domain,reject_authenticated_sender_login_mismatch,reject_rhsbl_sender dbl.spamhaus.org" \
|
smtpd_sender_restrictions="reject_non_fqdn_sender,reject_unknown_sender_domain,reject_authenticated_sender_login_mismatch,reject_rhsbl_sender dbl.spamhaus.org" \
|
||||||
smtpd_recipient_restrictions=permit_sasl_authenticated,permit_mynetworks,"reject_rbl_client zen.spamhaus.org",reject_unlisted_recipient,"check_policy_service inet:127.0.0.1:10023"
|
smtpd_recipient_restrictions=permit_sasl_authenticated,permit_mynetworks,"reject_rbl_client zen.spamhaus.org",reject_unlisted_recipient,"check_policy_service inet:127.0.0.1:10023"
|
||||||
|
|
||||||
@ -225,7 +225,7 @@ tools/editconf.py /etc/postfix/main.cf \
|
|||||||
# other MTA have their own intervals. To fix the problem of receiving
|
# other MTA have their own intervals. To fix the problem of receiving
|
||||||
# e-mails really latter, delay of greylisting has been set to
|
# e-mails really latter, delay of greylisting has been set to
|
||||||
# 180 seconds (default is 300 seconds).
|
# 180 seconds (default is 300 seconds).
|
||||||
tools/editconf.py /etc/default/postgrey \
|
management/editconf.py /etc/default/postgrey \
|
||||||
POSTGREY_OPTS=\"'--inet=127.0.0.1:10023 --delay=180'\"
|
POSTGREY_OPTS=\"'--inet=127.0.0.1:10023 --delay=180'\"
|
||||||
|
|
||||||
|
|
||||||
@ -257,9 +257,22 @@ chmod +x /etc/cron.daily/mailinabox-postgrey-whitelist
|
|||||||
|
|
||||||
# Increase the message size limit from 10MB to 128MB.
|
# Increase the message size limit from 10MB to 128MB.
|
||||||
# The same limit is specified in nginx.conf for mail submitted via webmail and Z-Push.
|
# The same limit is specified in nginx.conf for mail submitted via webmail and Z-Push.
|
||||||
tools/editconf.py /etc/postfix/main.cf \
|
management/editconf.py /etc/postfix/main.cf \
|
||||||
message_size_limit=134217728
|
message_size_limit=134217728
|
||||||
|
|
||||||
|
# Store default configurations for SMTP relays:
|
||||||
|
management/editconf.py /etc/postfix/main.cf \
|
||||||
|
smtp_sasl_auth_enable=no \
|
||||||
|
smtp_sasl_password_maps="hash:/etc/postfix/sasl_passwd" \
|
||||||
|
smtp_sasl_security_options=anonymous \
|
||||||
|
smtp_sasl_tls_security_options=anonymous \
|
||||||
|
smtp_tls_security_level=encrypt \
|
||||||
|
header_size_limit=4096000
|
||||||
|
|
||||||
|
touch /etc/postfix/sasl_passwd
|
||||||
|
chmod 600 /etc/postfix/sasl_passwd
|
||||||
|
postmap /etc/postfix/sasl_passwd
|
||||||
|
|
||||||
# Allow the two SMTP ports in the firewall.
|
# Allow the two SMTP ports in the firewall.
|
||||||
|
|
||||||
ufw_allow smtp
|
ufw_allow smtp
|
||||||
|
@ -71,7 +71,7 @@ EOF
|
|||||||
# does not run DKIM on relayed mail, so outbound mail isn't
|
# does not run DKIM on relayed mail, so outbound mail isn't
|
||||||
# correct, see #830), but we enable it specifically for the
|
# correct, see #830), but we enable it specifically for the
|
||||||
# submission port.
|
# submission port.
|
||||||
tools/editconf.py /etc/postfix/main.cf \
|
management/editconf.py /etc/postfix/main.cf \
|
||||||
smtpd_sasl_type=dovecot \
|
smtpd_sasl_type=dovecot \
|
||||||
smtpd_sasl_path=private/auth \
|
smtpd_sasl_path=private/auth \
|
||||||
smtpd_sasl_auth_enable=no
|
smtpd_sasl_auth_enable=no
|
||||||
@ -84,7 +84,7 @@ tools/editconf.py /etc/postfix/main.cf \
|
|||||||
# address (aka envelope or return path address) must be "owned" by the user
|
# address (aka envelope or return path address) must be "owned" by the user
|
||||||
# who authenticated. An SQL query will find who are the owners of any given
|
# who authenticated. An SQL query will find who are the owners of any given
|
||||||
# address.
|
# address.
|
||||||
tools/editconf.py /etc/postfix/main.cf \
|
management/editconf.py /etc/postfix/main.cf \
|
||||||
smtpd_sender_login_maps=sqlite:/etc/postfix/sender-login-maps.cf
|
smtpd_sender_login_maps=sqlite:/etc/postfix/sender-login-maps.cf
|
||||||
|
|
||||||
# Postfix will query the exact address first, where the priority will be alias
|
# Postfix will query the exact address first, where the priority will be alias
|
||||||
@ -101,7 +101,7 @@ EOF
|
|||||||
|
|
||||||
# Use a Sqlite3 database to check whether a destination email address exists,
|
# Use a Sqlite3 database to check whether a destination email address exists,
|
||||||
# and to perform any email alias rewrites in Postfix.
|
# and to perform any email alias rewrites in Postfix.
|
||||||
tools/editconf.py /etc/postfix/main.cf \
|
management/editconf.py /etc/postfix/main.cf \
|
||||||
virtual_mailbox_domains=sqlite:/etc/postfix/virtual-mailbox-domains.cf \
|
virtual_mailbox_domains=sqlite:/etc/postfix/virtual-mailbox-domains.cf \
|
||||||
virtual_mailbox_maps=sqlite:/etc/postfix/virtual-mailbox-maps.cf \
|
virtual_mailbox_maps=sqlite:/etc/postfix/virtual-mailbox-maps.cf \
|
||||||
virtual_alias_maps=sqlite:/etc/postfix/virtual-alias-maps.cf \
|
virtual_alias_maps=sqlite:/etc/postfix/virtual-alias-maps.cf \
|
||||||
|
@ -25,7 +25,7 @@ done
|
|||||||
#
|
#
|
||||||
# certbot installs EFF's certbot which we use to
|
# certbot installs EFF's certbot which we use to
|
||||||
# provision free TLS certificates.
|
# provision free TLS certificates.
|
||||||
apt_install duplicity python-pip virtualenv certbot
|
apt_install duplicity python3-pip virtualenv certbot
|
||||||
|
|
||||||
# boto is used for amazon aws backups.
|
# boto is used for amazon aws backups.
|
||||||
# Both are installed outside the pipenv, so they can be used by duplicity
|
# Both are installed outside the pipenv, so they can be used by duplicity
|
||||||
@ -69,22 +69,32 @@ rm -rf $assets_dir
|
|||||||
mkdir -p $assets_dir
|
mkdir -p $assets_dir
|
||||||
|
|
||||||
# jQuery CDN URL
|
# jQuery CDN URL
|
||||||
jquery_version=2.1.4
|
jquery_version=3.5.1
|
||||||
jquery_url=https://code.jquery.com
|
jquery_url=https://code.jquery.com
|
||||||
|
|
||||||
# Get jQuery
|
# Get jQuery
|
||||||
wget_verify $jquery_url/jquery-$jquery_version.min.js 43dc554608df885a59ddeece1598c6ace434d747 $assets_dir/jquery.min.js
|
wget_verify $jquery_url/jquery-$jquery_version.min.js c8e1c8b386dc5b7a9184c763c88d19a346eb3342 $assets_dir/jquery.min.js
|
||||||
|
|
||||||
# Bootstrap CDN URL
|
# Bootstrap CDN URL
|
||||||
bootstrap_version=3.3.7
|
bootstrap_version=4.6.0
|
||||||
bootstrap_url=https://github.com/twbs/bootstrap/releases/download/v$bootstrap_version/bootstrap-$bootstrap_version-dist.zip
|
bootstrap_url=https://github.com/twbs/bootstrap/releases/download/v$bootstrap_version/bootstrap-$bootstrap_version-dist.zip
|
||||||
|
|
||||||
# Get Bootstrap
|
# Get Bootstrap
|
||||||
wget_verify $bootstrap_url e6b1000b94e835ffd37f4c6dcbdad43f4b48a02a /tmp/bootstrap.zip
|
wget_verify $bootstrap_url a1d385dc33cb415512d2f38215a554c4380dac2d /tmp/bootstrap.zip
|
||||||
unzip -q /tmp/bootstrap.zip -d $assets_dir
|
unzip -q /tmp/bootstrap.zip -d $assets_dir
|
||||||
mv $assets_dir/bootstrap-$bootstrap_version-dist $assets_dir/bootstrap
|
mv $assets_dir/bootstrap-$bootstrap_version-dist $assets_dir/bootstrap
|
||||||
rm -f /tmp/bootstrap.zip
|
rm -f /tmp/bootstrap.zip
|
||||||
|
|
||||||
|
# FontAwesome CDN URL
|
||||||
|
fontawesome_version=5.15.2
|
||||||
|
fontawesome_url=https://github.com/FortAwesome/Font-Awesome/releases/download/$fontawesome_version/fontawesome-free-$fontawesome_version-web.zip
|
||||||
|
|
||||||
|
# Get FontAwesome
|
||||||
|
wget_verify $fontawesome_url 2f0b3f88500238fa0be798d628a3e68c5784f165 /tmp/fontawesome.zip
|
||||||
|
unzip -q /tmp/fontawesome.zip -d $assets_dir
|
||||||
|
mv $assets_dir/fontawesome-free-$fontawesome_version-web $assets_dir/fontawesome
|
||||||
|
rm -f /tmp/fontawesome.zip
|
||||||
|
|
||||||
# Create an init script to start the management daemon and keep it
|
# Create an init script to start the management daemon and keep it
|
||||||
# running after a reboot.
|
# running after a reboot.
|
||||||
cat > $inst_dir/start <<EOF;
|
cat > $inst_dir/start <<EOF;
|
||||||
@ -116,3 +126,14 @@ EOF
|
|||||||
|
|
||||||
# Start the management server.
|
# Start the management server.
|
||||||
restart_service mailinabox
|
restart_service mailinabox
|
||||||
|
|
||||||
|
# FOR DEVELOPMENT PURPOSES ONLY:
|
||||||
|
# If there is a CA certificate in the folder, install it.
|
||||||
|
# MIAB will only accept a manual certificate installation
|
||||||
|
# if it is signed by a CA trusted by it.
|
||||||
|
if [[ -f mailinabox-ca.crt ]]; then
|
||||||
|
echo "Custom CA certificate detected. Installing..."
|
||||||
|
rm -f /usr/local/share/ca-certificates/mailinabox-ca.crt
|
||||||
|
cp mailinabox-ca.crt /usr/local/share/ca-certificates/
|
||||||
|
update-ca-certificates --fresh
|
||||||
|
fi
|
||||||
|
@ -39,7 +39,7 @@ chown munin. /var/log/munin/munin-cgi-graph.log
|
|||||||
|
|
||||||
# ensure munin-node knows the name of this machine
|
# ensure munin-node knows the name of this machine
|
||||||
# and reduce logging level to warning
|
# and reduce logging level to warning
|
||||||
tools/editconf.py /etc/munin/munin-node.conf -s \
|
management/editconf.py /etc/munin/munin-node.conf -s \
|
||||||
host_name=$PRIMARY_HOSTNAME \
|
host_name=$PRIMARY_HOSTNAME \
|
||||||
log_level=1
|
log_level=1
|
||||||
|
|
||||||
|
@ -97,12 +97,12 @@ InstallNextcloud() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Nextcloud Version to install. Checks are done down below to step through intermediate versions.
|
# Nextcloud Version to install. Checks are done down below to step through intermediate versions.
|
||||||
nextcloud_ver=20.0.1
|
nextcloud_ver=20.0.6
|
||||||
nextcloud_hash=f2b3faa570c541df73f209e873a1c2852e79eab8
|
nextcloud_hash=3c0e6ffbbcb125be282098253793ee6cf07658ba
|
||||||
contacts_ver=3.4.1
|
contacts_ver=3.4.3
|
||||||
contacts_hash=aee680a75e95f26d9285efd3c1e25cf7f3bfd27e
|
contacts_hash=e21488cd8608f876517e00d0b36b21c0f2dbaf50
|
||||||
calendar_ver=2.1.2
|
calendar_ver=2.1.3
|
||||||
calendar_hash=930c07863bb7a65652dec34793802c8d80502336
|
calendar_hash=d7d9db0e55ff1c9c2a2356e8980a8d9fce3fc4a0
|
||||||
user_external_ver=1.0.0
|
user_external_ver=1.0.0
|
||||||
user_external_hash=3bf2609061d7214e7f0f69dd8883e55c4ec8f50a
|
user_external_hash=3bf2609061d7214e7f0f69dd8883e55c4ec8f50a
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ fi
|
|||||||
if [ ! -d /usr/local/lib/owncloud/ ] || [[ ! ${CURRENT_NEXTCLOUD_VER} =~ ^$nextcloud_ver ]]; then
|
if [ ! -d /usr/local/lib/owncloud/ ] || [[ ! ${CURRENT_NEXTCLOUD_VER} =~ ^$nextcloud_ver ]]; then
|
||||||
|
|
||||||
# Stop php-fpm if running. If theyre not running (which happens on a previously failed install), dont bail.
|
# Stop php-fpm if running. If theyre not running (which happens on a previously failed install), dont bail.
|
||||||
service php7.2-fpm stop &> /dev/null || /bin/true
|
service php$(php_version)-fpm stop &> /dev/null || /bin/true
|
||||||
|
|
||||||
# Backup the existing ownCloud/Nextcloud.
|
# Backup the existing ownCloud/Nextcloud.
|
||||||
# Create a backup directory to store the current installation and database to
|
# Create a backup directory to store the current installation and database to
|
||||||
@ -316,7 +316,7 @@ sudo -u www-data php /usr/local/lib/owncloud/occ app:disable photos dashboard ac
|
|||||||
|
|
||||||
# Set PHP FPM values to support large file uploads
|
# Set PHP FPM values to support large file uploads
|
||||||
# (semicolon is the comment character in this file, hashes produce deprecation warnings)
|
# (semicolon is the comment character in this file, hashes produce deprecation warnings)
|
||||||
tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
|
management/editconf.py /etc/php/$(php_version)/fpm/php.ini -c ';' \
|
||||||
upload_max_filesize=16G \
|
upload_max_filesize=16G \
|
||||||
post_max_size=16G \
|
post_max_size=16G \
|
||||||
output_buffering=16384 \
|
output_buffering=16384 \
|
||||||
@ -325,7 +325,7 @@ tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
|
|||||||
short_open_tag=On
|
short_open_tag=On
|
||||||
|
|
||||||
# Set Nextcloud recommended opcache settings
|
# Set Nextcloud recommended opcache settings
|
||||||
tools/editconf.py /etc/php/7.2/cli/conf.d/10-opcache.ini -c ';' \
|
management/editconf.py /etc/php/$(php_version)/cli/conf.d/10-opcache.ini -c ';' \
|
||||||
opcache.enable=1 \
|
opcache.enable=1 \
|
||||||
opcache.enable_cli=1 \
|
opcache.enable_cli=1 \
|
||||||
opcache.interned_strings_buffer=8 \
|
opcache.interned_strings_buffer=8 \
|
||||||
@ -335,8 +335,8 @@ tools/editconf.py /etc/php/7.2/cli/conf.d/10-opcache.ini -c ';' \
|
|||||||
opcache.revalidate_freq=1
|
opcache.revalidate_freq=1
|
||||||
|
|
||||||
# If apc is explicitly disabled we need to enable it
|
# If apc is explicitly disabled we need to enable it
|
||||||
if grep -q apc.enabled=0 /etc/php/7.2/mods-available/apcu.ini; then
|
if grep -q apc.enabled=0 /etc/php/$(php_version)/mods-available/apcu.ini; then
|
||||||
tools/editconf.py /etc/php/7.2/mods-available/apcu.ini -c ';' \
|
management/editconf.py /etc/php/$(php_version)/mods-available/apcu.ini -c ';' \
|
||||||
apc.enabled=1
|
apc.enabled=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -361,4 +361,4 @@ rm -f /etc/cron.hourly/mailinabox-owncloud
|
|||||||
# ```
|
# ```
|
||||||
|
|
||||||
# Enable PHP modules and restart PHP.
|
# Enable PHP modules and restart PHP.
|
||||||
restart_service php7.2-fpm
|
restart_service php$(php_version)-fpm
|
||||||
|
@ -7,9 +7,10 @@ if [[ $EUID -ne 0 ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check that we are running on Ubuntu 18.04 LTS (or 18.04.xx).
|
# Check that we are running on Debian GNU/Linux, or Ubuntu 20.04
|
||||||
if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" != "Ubuntu 18.04 LTS" ]; then
|
OS=`lsb_release -d | sed 's/.*:\s*//'`
|
||||||
echo "Mail-in-a-Box only supports being installed on Ubuntu 18.04, sorry. You are running:"
|
if [ "$OS" != "Debian GNU/Linux 10 (buster)" -a "$(echo $OS | grep -o 'Ubuntu 20.04')" != "Ubuntu 20.04" ]; then
|
||||||
|
echo "Mail-in-a-Box only supports being installed on Debian 10 or Ubuntu 20.04 LTS, sorry. You are running:"
|
||||||
echo
|
echo
|
||||||
lsb_release -d | sed 's/.*:\s*//'
|
lsb_release -d | sed 's/.*:\s*//'
|
||||||
echo
|
echo
|
||||||
|
@ -9,7 +9,7 @@ if [ -z "${NONINTERACTIVE:-}" ]; then
|
|||||||
if [ ! -f /usr/bin/dialog ] || [ ! -f /usr/bin/python3 ] || [ ! -f /usr/bin/pip3 ]; then
|
if [ ! -f /usr/bin/dialog ] || [ ! -f /usr/bin/python3 ] || [ ! -f /usr/bin/pip3 ]; then
|
||||||
echo Installing packages needed for setup...
|
echo Installing packages needed for setup...
|
||||||
apt-get -q -q update
|
apt-get -q -q update
|
||||||
apt_get_quiet install dialog python3 python3-pip || exit 1
|
apt_get_quiet install dialog file python3 python3-pip || exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Installing email_validator is repeated in setup/management.sh, but in setup/management.sh
|
# Installing email_validator is repeated in setup/management.sh, but in setup/management.sh
|
||||||
@ -18,10 +18,10 @@ if [ -z "${NONINTERACTIVE:-}" ]; then
|
|||||||
hide_output pip3 install "email_validator>=1.0.0" || exit 1
|
hide_output pip3 install "email_validator>=1.0.0" || exit 1
|
||||||
|
|
||||||
message_box "Mail-in-a-Box Installation" \
|
message_box "Mail-in-a-Box Installation" \
|
||||||
"Hello and thanks for deploying a Mail-in-a-Box!
|
"Hello and thanks for deploying a (Power) Mail-in-a-Box!
|
||||||
\n\nI'm going to ask you a few questions.
|
\n\nI'm going to ask you a few questions.
|
||||||
\n\nTo change your answers later, just run 'sudo mailinabox' from the command line.
|
\n\nTo change your answers later, just run 'sudo mailinabox' from the command line.
|
||||||
\n\nNOTE: You should only install this on a brand new Ubuntu installation 100% dedicated to Mail-in-a-Box. Mail-in-a-Box will, for example, remove apache2."
|
\n\nNOTE: You should only install this on a brand new Debian/Ubuntu installation 100% dedicated to Mail-in-a-Box. Mail-in-a-Box will, for example, remove apache2."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# The box needs a name.
|
# The box needs a name.
|
||||||
@ -207,6 +207,6 @@ if [ "$PRIVATE_IPV6" != "$PUBLIC_IPV6" ]; then
|
|||||||
echo "Private IPv6 Address: $PRIVATE_IPV6"
|
echo "Private IPv6 Address: $PRIVATE_IPV6"
|
||||||
fi
|
fi
|
||||||
if [ -f /usr/bin/git ] && [ -d .git ]; then
|
if [ -f /usr/bin/git ] && [ -d .git ]; then
|
||||||
echo "Mail-in-a-Box Version: " $(git describe)
|
echo "Mail-in-a-Box Version: " $(git describe --tags)
|
||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
|
@ -23,7 +23,7 @@ echo "Installing SpamAssassin..."
|
|||||||
apt_install spampd razor pyzor dovecot-antispam libmail-dkim-perl
|
apt_install spampd razor pyzor dovecot-antispam libmail-dkim-perl
|
||||||
|
|
||||||
# Allow spamassassin to download new rules.
|
# Allow spamassassin to download new rules.
|
||||||
tools/editconf.py /etc/default/spamassassin \
|
management/editconf.py /etc/default/spamassassin \
|
||||||
CRON=1
|
CRON=1
|
||||||
|
|
||||||
# Configure pyzor, which is a client to a live database of hashes of
|
# Configure pyzor, which is a client to a live database of hashes of
|
||||||
@ -34,7 +34,7 @@ tools/editconf.py /etc/default/spamassassin \
|
|||||||
# we can skip 'pyzor discover', both of which are currently broken by
|
# we can skip 'pyzor discover', both of which are currently broken by
|
||||||
# something happening on Sourceforge (#496).
|
# something happening on Sourceforge (#496).
|
||||||
rm -rf ~/.pyzor
|
rm -rf ~/.pyzor
|
||||||
tools/editconf.py /etc/spamassassin/local.cf -s \
|
management/editconf.py /etc/spamassassin/local.cf -s \
|
||||||
pyzor_options="--homedir /etc/spamassassin/pyzor"
|
pyzor_options="--homedir /etc/spamassassin/pyzor"
|
||||||
mkdir -p /etc/spamassassin/pyzor
|
mkdir -p /etc/spamassassin/pyzor
|
||||||
echo "public.pyzor.org:24441" > /etc/spamassassin/pyzor/servers
|
echo "public.pyzor.org:24441" > /etc/spamassassin/pyzor/servers
|
||||||
@ -46,7 +46,7 @@ echo "public.pyzor.org:24441" > /etc/spamassassin/pyzor/servers
|
|||||||
# * Increase the maximum message size of scanned messages from the default of 64KB to 500KB, which
|
# * Increase the maximum message size of scanned messages from the default of 64KB to 500KB, which
|
||||||
# is Spamassassin (spamc)'s own default. Specified in KBytes.
|
# is Spamassassin (spamc)'s own default. Specified in KBytes.
|
||||||
# * 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 \
|
management/editconf.py /etc/default/spampd \
|
||||||
DESTPORT=10026 \
|
DESTPORT=10026 \
|
||||||
ADDOPTS="\"--maxsize=2000\"" \
|
ADDOPTS="\"--maxsize=2000\"" \
|
||||||
LOCALONLY=0
|
LOCALONLY=0
|
||||||
@ -62,7 +62,7 @@ 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 & X-Spam-Score mail headers and related headers.
|
# the X-Spam-Status & X-Spam-Score mail headers and related headers.
|
||||||
tools/editconf.py /etc/spamassassin/local.cf -s \
|
management/editconf.py /etc/spamassassin/local.cf -s \
|
||||||
report_safe=0 \
|
report_safe=0 \
|
||||||
"add_header all Report"=_REPORT_ \
|
"add_header all Report"=_REPORT_ \
|
||||||
"add_header all Score"=_SCORE_
|
"add_header all Score"=_SCORE_
|
||||||
@ -134,7 +134,7 @@ EOF
|
|||||||
# Spamassassin will change the access rights back to the defaults, so we must also configure
|
# Spamassassin will change the access rights back to the defaults, so we must also configure
|
||||||
# the filemode in the config file.
|
# the filemode in the config file.
|
||||||
|
|
||||||
tools/editconf.py /etc/spamassassin/local.cf -s \
|
management/editconf.py /etc/spamassassin/local.cf -s \
|
||||||
bayes_path=$STORAGE_ROOT/mail/spamassassin/bayes \
|
bayes_path=$STORAGE_ROOT/mail/spamassassin/bayes \
|
||||||
bayes_file_mode=0666
|
bayes_file_mode=0666
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ EOF
|
|||||||
# Have Dovecot run its mail process with a supplementary group (the spampd group)
|
# Have Dovecot run its mail process with a supplementary group (the spampd group)
|
||||||
# so that it can access the learning files.
|
# so that it can access the learning files.
|
||||||
|
|
||||||
tools/editconf.py /etc/dovecot/conf.d/10-mail.conf \
|
management/editconf.py /etc/dovecot/conf.d/10-mail.conf \
|
||||||
mail_access_groups=spampd
|
mail_access_groups=spampd
|
||||||
|
|
||||||
# Here's the script that the antispam plugin executes. It spools the message into
|
# Here's the script that the antispam plugin executes. It spools the message into
|
||||||
|
@ -14,9 +14,14 @@ source setup/preflight.sh
|
|||||||
# Python may not be able to read/write files. This is also
|
# Python may not be able to read/write files. This is also
|
||||||
# in the management daemon startup script and the cron script.
|
# in the management daemon startup script and the cron script.
|
||||||
|
|
||||||
|
# Make sure we have locales at all (some images are THAT minimal)
|
||||||
|
apt_get_quiet install locales
|
||||||
|
|
||||||
if ! locale -a | grep en_US.utf8 > /dev/null; then
|
if ! locale -a | grep en_US.utf8 > /dev/null; then
|
||||||
|
echo "Generating locales..."
|
||||||
# Generate locale if not exists
|
# Generate locale if not exists
|
||||||
hide_output locale-gen en_US.UTF-8
|
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
|
||||||
|
hide_output locale-gen
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export LANGUAGE=en_US.UTF-8
|
export LANGUAGE=en_US.UTF-8
|
||||||
|
@ -75,26 +75,7 @@ then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ### Add PPAs.
|
# Certbot doesn't require a PPA in Debian
|
||||||
|
|
||||||
# We install some non-standard Ubuntu packages maintained by other
|
|
||||||
# third-party providers. First ensure add-apt-repository is installed.
|
|
||||||
|
|
||||||
if [ ! -f /usr/bin/add-apt-repository ]; then
|
|
||||||
echo "Installing add-apt-repository..."
|
|
||||||
hide_output apt-get update
|
|
||||||
apt_install software-properties-common
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ensure the universe repository is enabled since some of our packages
|
|
||||||
# come from there and minimal Ubuntu installs may have it turned off.
|
|
||||||
hide_output add-apt-repository -y universe
|
|
||||||
|
|
||||||
# Install the certbot PPA.
|
|
||||||
hide_output add-apt-repository -y ppa:certbot/certbot
|
|
||||||
|
|
||||||
# Install the duplicity PPA.
|
|
||||||
hide_output add-apt-repository -y ppa:duplicity-team/duplicity-release-git
|
|
||||||
|
|
||||||
# ### Update Packages
|
# ### Update Packages
|
||||||
|
|
||||||
@ -140,7 +121,7 @@ apt_install python3 python3-dev python3-pip \
|
|||||||
# When Ubuntu 20 comes out, we don't want users to be prompted to upgrade,
|
# When Ubuntu 20 comes out, we don't want users to be prompted to upgrade,
|
||||||
# because we don't yet support it.
|
# because we don't yet support it.
|
||||||
if [ -f /etc/update-manager/release-upgrades ]; then
|
if [ -f /etc/update-manager/release-upgrades ]; then
|
||||||
tools/editconf.py /etc/update-manager/release-upgrades Prompt=never
|
management/editconf.py /etc/update-manager/release-upgrades Prompt=never
|
||||||
rm -f /var/lib/ubuntu-release-upgrader/release-upgrade-available
|
rm -f /var/lib/ubuntu-release-upgrader/release-upgrade-available
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -324,7 +305,8 @@ fi #NODOC
|
|||||||
# If more queries than specified are sent, bind9 returns SERVFAIL. After flushing the cache during system checks,
|
# If more queries than specified are sent, bind9 returns SERVFAIL. After flushing the cache during system checks,
|
||||||
# we ran into the limit thus we are increasing it from 75 (default value) to 100.
|
# we ran into the limit thus we are increasing it from 75 (default value) to 100.
|
||||||
apt_install bind9
|
apt_install bind9
|
||||||
tools/editconf.py /etc/default/bind9 \
|
touch /etc/default/bind9
|
||||||
|
management/editconf.py /etc/default/bind9 \
|
||||||
"OPTIONS=\"-u bind -4\""
|
"OPTIONS=\"-u bind -4\""
|
||||||
if ! grep -q "listen-on " /etc/bind/named.conf.options; then
|
if ! grep -q "listen-on " /etc/bind/named.conf.options; then
|
||||||
# Add a listen-on directive if it doesn't exist inside the options block.
|
# Add a listen-on directive if it doesn't exist inside the options block.
|
||||||
@ -342,7 +324,7 @@ fi
|
|||||||
# installing bind9 or else apt won't be able to resolve a server to
|
# installing bind9 or else apt won't be able to resolve a server to
|
||||||
# download bind9 from.
|
# download bind9 from.
|
||||||
rm -f /etc/resolv.conf
|
rm -f /etc/resolv.conf
|
||||||
tools/editconf.py /etc/systemd/resolved.conf DNSStubListener=no
|
management/editconf.py /etc/systemd/resolved.conf DNSStubListener=no
|
||||||
echo "nameserver 127.0.0.1" > /etc/resolv.conf
|
echo "nameserver 127.0.0.1" > /etc/resolv.conf
|
||||||
|
|
||||||
# Restart the DNS services.
|
# Restart the DNS services.
|
||||||
|
18
setup/web.sh
18
setup/web.sh
@ -41,20 +41,20 @@ sed "s#STORAGE_ROOT#$STORAGE_ROOT#" \
|
|||||||
#
|
#
|
||||||
# Drop TLSv1.0, TLSv1.1, following the Mozilla "Intermediate" recommendations
|
# Drop TLSv1.0, TLSv1.1, following the Mozilla "Intermediate" recommendations
|
||||||
# at https://ssl-config.mozilla.org/#server=nginx&server-version=1.17.0&config=intermediate&openssl-version=1.1.1.
|
# at https://ssl-config.mozilla.org/#server=nginx&server-version=1.17.0&config=intermediate&openssl-version=1.1.1.
|
||||||
tools/editconf.py /etc/nginx/nginx.conf -s \
|
management/editconf.py /etc/nginx/nginx.conf -s \
|
||||||
server_names_hash_bucket_size="128;" \
|
server_names_hash_bucket_size="128;" \
|
||||||
ssl_protocols="TLSv1.2 TLSv1.3;"
|
ssl_protocols="TLSv1.2 TLSv1.3;"
|
||||||
|
|
||||||
# Tell PHP not to expose its version number in the X-Powered-By header.
|
# Tell PHP not to expose its version number in the X-Powered-By header.
|
||||||
tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
|
management/editconf.py /etc/php/$(php_version)/fpm/php.ini -c ';' \
|
||||||
expose_php=Off
|
expose_php=Off
|
||||||
|
|
||||||
# Set PHPs default charset to UTF-8, since we use it. See #367.
|
# Set PHPs default charset to UTF-8, since we use it. See #367.
|
||||||
tools/editconf.py /etc/php/7.2/fpm/php.ini -c ';' \
|
management/editconf.py /etc/php/$(php_version)/fpm/php.ini -c ';' \
|
||||||
default_charset="UTF-8"
|
default_charset="UTF-8"
|
||||||
|
|
||||||
# Configure the path environment for php-fpm
|
# Configure the path environment for php-fpm
|
||||||
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
|
management/editconf.py /etc/php/$(php_version)/fpm/pool.d/www.conf -c ';' \
|
||||||
env[PATH]=/usr/local/bin:/usr/bin:/bin \
|
env[PATH]=/usr/local/bin:/usr/bin:/bin \
|
||||||
|
|
||||||
# Configure php-fpm based on the amount of memory the machine has
|
# Configure php-fpm based on the amount of memory the machine has
|
||||||
@ -64,7 +64,7 @@ tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
|
|||||||
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}' || /bin/true)
|
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}' || /bin/true)
|
||||||
if [ $TOTAL_PHYSICAL_MEM -lt 1000000 ]
|
if [ $TOTAL_PHYSICAL_MEM -lt 1000000 ]
|
||||||
then
|
then
|
||||||
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
|
management/editconf.py /etc/php/$(php_version)/fpm/pool.d/www.conf -c ';' \
|
||||||
pm=ondemand \
|
pm=ondemand \
|
||||||
pm.max_children=8 \
|
pm.max_children=8 \
|
||||||
pm.start_servers=2 \
|
pm.start_servers=2 \
|
||||||
@ -72,7 +72,7 @@ then
|
|||||||
pm.max_spare_servers=3
|
pm.max_spare_servers=3
|
||||||
elif [ $TOTAL_PHYSICAL_MEM -lt 2000000 ]
|
elif [ $TOTAL_PHYSICAL_MEM -lt 2000000 ]
|
||||||
then
|
then
|
||||||
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
|
management/editconf.py /etc/php/$(php_version)/fpm/pool.d/www.conf -c ';' \
|
||||||
pm=ondemand \
|
pm=ondemand \
|
||||||
pm.max_children=16 \
|
pm.max_children=16 \
|
||||||
pm.start_servers=4 \
|
pm.start_servers=4 \
|
||||||
@ -80,14 +80,14 @@ then
|
|||||||
pm.max_spare_servers=6
|
pm.max_spare_servers=6
|
||||||
elif [ $TOTAL_PHYSICAL_MEM -lt 3000000 ]
|
elif [ $TOTAL_PHYSICAL_MEM -lt 3000000 ]
|
||||||
then
|
then
|
||||||
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
|
management/editconf.py /etc/php/$(php_version)/fpm/pool.d/www.conf -c ';' \
|
||||||
pm=dynamic \
|
pm=dynamic \
|
||||||
pm.max_children=60 \
|
pm.max_children=60 \
|
||||||
pm.start_servers=6 \
|
pm.start_servers=6 \
|
||||||
pm.min_spare_servers=3 \
|
pm.min_spare_servers=3 \
|
||||||
pm.max_spare_servers=9
|
pm.max_spare_servers=9
|
||||||
else
|
else
|
||||||
tools/editconf.py /etc/php/7.2/fpm/pool.d/www.conf -c ';' \
|
management/editconf.py /etc/php/$(php_version)/fpm/pool.d/www.conf -c ';' \
|
||||||
pm=dynamic \
|
pm=dynamic \
|
||||||
pm.max_children=120 \
|
pm.max_children=120 \
|
||||||
pm.start_servers=12 \
|
pm.start_servers=12 \
|
||||||
@ -147,7 +147,7 @@ chown -R $STORAGE_USER $STORAGE_ROOT/www
|
|||||||
|
|
||||||
# Start services.
|
# Start services.
|
||||||
restart_service nginx
|
restart_service nginx
|
||||||
restart_service php7.2-fpm
|
restart_service php$(php_version)-fpm
|
||||||
|
|
||||||
# Open ports.
|
# Open ports.
|
||||||
ufw_allow http
|
ufw_allow http
|
||||||
|
@ -168,7 +168,7 @@ sudo -u www-data touch /var/log/roundcubemail/errors.log
|
|||||||
cp ${RCM_PLUGIN_DIR}/password/config.inc.php.dist \
|
cp ${RCM_PLUGIN_DIR}/password/config.inc.php.dist \
|
||||||
${RCM_PLUGIN_DIR}/password/config.inc.php
|
${RCM_PLUGIN_DIR}/password/config.inc.php
|
||||||
|
|
||||||
tools/editconf.py ${RCM_PLUGIN_DIR}/password/config.inc.php \
|
management/editconf.py ${RCM_PLUGIN_DIR}/password/config.inc.php \
|
||||||
"\$config['password_minimum_length']=8;" \
|
"\$config['password_minimum_length']=8;" \
|
||||||
"\$config['password_db_dsn']='sqlite:///$STORAGE_ROOT/mail/users.sqlite';" \
|
"\$config['password_db_dsn']='sqlite:///$STORAGE_ROOT/mail/users.sqlite';" \
|
||||||
"\$config['password_query']='UPDATE users SET password=%D WHERE email=%u';" \
|
"\$config['password_query']='UPDATE users SET password=%D WHERE email=%u';" \
|
||||||
@ -198,4 +198,4 @@ chmod 664 $STORAGE_ROOT/mail/roundcube/roundcube.sqlite
|
|||||||
|
|
||||||
# Enable PHP modules.
|
# Enable PHP modules.
|
||||||
phpenmod -v php mcrypt imap
|
phpenmod -v php mcrypt imap
|
||||||
restart_service php7.2-fpm
|
restart_service php$(php_version)-fpm
|
||||||
|
@ -102,7 +102,7 @@ EOF
|
|||||||
|
|
||||||
# Restart service.
|
# Restart service.
|
||||||
|
|
||||||
restart_service php7.2-fpm
|
restart_service php$(php_version)-fpm
|
||||||
|
|
||||||
# Fix states after upgrade
|
# Fix states after upgrade
|
||||||
|
|
||||||
|
@ -1,137 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
#
|
|
||||||
# This is a helper tool for editing configuration files during the setup
|
|
||||||
# process. The tool is given new values for settings as command-line
|
|
||||||
# arguments. It comments-out existing setting values in the configuration
|
|
||||||
# file and adds new values either after their former location or at the
|
|
||||||
# end.
|
|
||||||
#
|
|
||||||
# The configuration file has settings that look like:
|
|
||||||
#
|
|
||||||
# NAME=VALUE
|
|
||||||
#
|
|
||||||
# If the -s option is given, then space becomes the delimiter, i.e.:
|
|
||||||
#
|
|
||||||
# NAME VALUE
|
|
||||||
#
|
|
||||||
# If the -c option is given, then the supplied character becomes the comment character
|
|
||||||
#
|
|
||||||
# If the -w option is given, then setting lines continue onto following
|
|
||||||
# lines while the lines start with whitespace, e.g.:
|
|
||||||
#
|
|
||||||
# NAME VAL
|
|
||||||
# UE
|
|
||||||
|
|
||||||
import sys, re
|
|
||||||
|
|
||||||
# sanity check
|
|
||||||
if len(sys.argv) < 3:
|
|
||||||
print("usage: python3 editconf.py /etc/file.conf [-s] [-w] [-c <CHARACTER>] [-t] NAME=VAL [NAME=VAL ...]")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# parse command line arguments
|
|
||||||
filename = sys.argv[1]
|
|
||||||
settings = sys.argv[2:]
|
|
||||||
|
|
||||||
delimiter = "="
|
|
||||||
delimiter_re = r"\s*=\s*"
|
|
||||||
comment_char = "#"
|
|
||||||
folded_lines = False
|
|
||||||
testing = False
|
|
||||||
while settings[0][0] == "-" and settings[0] != "--":
|
|
||||||
opt = settings.pop(0)
|
|
||||||
if opt == "-s":
|
|
||||||
# Space is the delimiter
|
|
||||||
delimiter = " "
|
|
||||||
delimiter_re = r"\s+"
|
|
||||||
elif opt == "-w":
|
|
||||||
# Line folding is possible in this file.
|
|
||||||
folded_lines = True
|
|
||||||
elif opt == "-c":
|
|
||||||
# Specifies a different comment character.
|
|
||||||
comment_char = settings.pop(0)
|
|
||||||
elif opt == "-t":
|
|
||||||
testing = True
|
|
||||||
else:
|
|
||||||
print("Invalid option.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# sanity check command line
|
|
||||||
for setting in settings:
|
|
||||||
try:
|
|
||||||
name, value = setting.split("=", 1)
|
|
||||||
except:
|
|
||||||
import subprocess
|
|
||||||
print("Invalid command line: ", subprocess.list2cmdline(sys.argv))
|
|
||||||
|
|
||||||
# create the new config file in memory
|
|
||||||
|
|
||||||
found = set()
|
|
||||||
buf = ""
|
|
||||||
input_lines = list(open(filename))
|
|
||||||
|
|
||||||
while len(input_lines) > 0:
|
|
||||||
line = input_lines.pop(0)
|
|
||||||
|
|
||||||
# If this configuration file uses folded lines, append any folded lines
|
|
||||||
# into our input buffer.
|
|
||||||
if folded_lines and line[0] not in (comment_char, " ", ""):
|
|
||||||
while len(input_lines) > 0 and input_lines[0][0] in " \t":
|
|
||||||
line += input_lines.pop(0)
|
|
||||||
|
|
||||||
# See if this line is for any settings passed on the command line.
|
|
||||||
for i in range(len(settings)):
|
|
||||||
# Check that this line contain this setting from the command-line arguments.
|
|
||||||
name, val = settings[i].split("=", 1)
|
|
||||||
m = re.match(
|
|
||||||
"(\s*)"
|
|
||||||
+ "(" + re.escape(comment_char) + "\s*)?"
|
|
||||||
+ re.escape(name) + delimiter_re + "(.*?)\s*$",
|
|
||||||
line, re.S)
|
|
||||||
if not m: continue
|
|
||||||
indent, is_comment, existing_val = m.groups()
|
|
||||||
|
|
||||||
# If this is already the setting, do nothing.
|
|
||||||
if is_comment is None and existing_val == val:
|
|
||||||
# It may be that we've already inserted this setting higher
|
|
||||||
# in the file so check for that first.
|
|
||||||
if i in found: break
|
|
||||||
buf += line
|
|
||||||
found.add(i)
|
|
||||||
break
|
|
||||||
|
|
||||||
# comment-out the existing line (also comment any folded lines)
|
|
||||||
if is_comment is None:
|
|
||||||
buf += comment_char + line.rstrip().replace("\n", "\n" + comment_char) + "\n"
|
|
||||||
else:
|
|
||||||
# the line is already commented, pass it through
|
|
||||||
buf += line
|
|
||||||
|
|
||||||
# if this option oddly appears more than once, don't add the setting again
|
|
||||||
if i in found:
|
|
||||||
break
|
|
||||||
|
|
||||||
# add the new setting
|
|
||||||
buf += indent + name + delimiter + val + "\n"
|
|
||||||
|
|
||||||
# note that we've applied this option
|
|
||||||
found.add(i)
|
|
||||||
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# If did not match any setting names, pass this line through.
|
|
||||||
buf += line
|
|
||||||
|
|
||||||
# Put any settings we didn't see at the end of the file.
|
|
||||||
for i in range(len(settings)):
|
|
||||||
if i not in found:
|
|
||||||
name, val = settings[i].split("=", 1)
|
|
||||||
buf += name + delimiter + val + "\n"
|
|
||||||
|
|
||||||
if not testing:
|
|
||||||
# Write out the new file.
|
|
||||||
with open(filename, "w") as f:
|
|
||||||
f.write(buf)
|
|
||||||
else:
|
|
||||||
# Just print the new file to stdout.
|
|
||||||
print(buf)
|
|
@ -26,7 +26,7 @@ if [ ! -f $1/config.php ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Restoring backup from $1"
|
echo "Restoring backup from $1"
|
||||||
service php7.2-fpm stop
|
service php7.3-fpm stop
|
||||||
|
|
||||||
# remove the current ownCloud/Nextcloud installation
|
# remove the current ownCloud/Nextcloud installation
|
||||||
rm -rf /usr/local/lib/owncloud/
|
rm -rf /usr/local/lib/owncloud/
|
||||||
@ -45,5 +45,5 @@ chown www-data.www-data $STORAGE_ROOT/owncloud/config.php
|
|||||||
|
|
||||||
sudo -u www-data php /usr/local/lib/owncloud/occ maintenance:mode --off
|
sudo -u www-data php /usr/local/lib/owncloud/occ maintenance:mode --off
|
||||||
|
|
||||||
service php7.2-fpm start
|
service php7.3-fpm start
|
||||||
echo "Done"
|
echo "Done"
|
||||||
|
Loading…
Reference in New Issue
Block a user