mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2024-12-25 07:47:05 +00:00
include changes from v0.53. Remove some POWER modifications to closer follow original mialinabox
This commit is contained in:
parent
40adef2261
commit
c24ca5abd4
@ -12,9 +12,6 @@ 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,4 +6,3 @@ externals/
|
|||||||
.env
|
.env
|
||||||
.vagrant
|
.vagrant
|
||||||
api/docs/api-docs.html
|
api/docs/api-docs.html
|
||||||
mailinabox-ca.crt
|
|
||||||
|
24
CHANGELOG.md
24
CHANGELOG.md
@ -1,13 +1,35 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
v0.53 (April 12, 2021)
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Software updates:
|
||||||
|
|
||||||
|
* Upgraded Roundcube to version 1.4.11 addressing a security issue, and its desktop notifications plugin.
|
||||||
|
* Upgraded Z-Push (for Exchange/ActiveSync) to version 2.6.2.
|
||||||
|
|
||||||
|
Control panel:
|
||||||
|
|
||||||
|
* Backblaze B2 is now a supported backup protocol.
|
||||||
|
* Fixed an issue in the daily mail reports.
|
||||||
|
* Sort the Custom DNS by zone and qname, and add an option to go back to the old sort order (creation order).
|
||||||
|
|
||||||
|
Mail:
|
||||||
|
|
||||||
|
* Enable sending DMARC failure reports to senders that request them.
|
||||||
|
|
||||||
|
Setup:
|
||||||
|
|
||||||
|
* Fixed error when upgrading from Nextcloud 13.
|
||||||
|
|
||||||
v0.52 (January 31, 2021)
|
v0.52 (January 31, 2021)
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
Software updates:
|
Software updates:
|
||||||
|
|
||||||
* Upgraded Roundcube to version 1.4.10.
|
* Upgraded Roundcube to version 1.4.10.
|
||||||
* Upgraded zpush to 2.6.1.
|
* Upgraded Z-Push to 2.6.1.
|
||||||
|
|
||||||
Mail:
|
Mail:
|
||||||
|
|
||||||
|
96
README.md
96
README.md
@ -1,86 +1,14 @@
|
|||||||
(Power) Mail-in-a-Box
|
This is not the original Mail-in-a-Box. See https://github.com/mail-in-a-box/mailinabox for the real deal! I made a number of modifications to to:
|
||||||
=====================
|
- add geoipblocking on the admin web console
|
||||||
|
- add geoipblocking for ssh access
|
||||||
|
- make fail2ban a more stricter
|
||||||
|
- add fail2ban filter for web scanners
|
||||||
|
- other small stuff
|
||||||
|
|
||||||
## Installation
|
Original mailinabox content starts here:
|
||||||
|
|
||||||
- **PRE-REQUISITES:** Debian 10 (Buster) or Ubuntu 20.04 LTS fresh installation
|
Mail-in-a-Box
|
||||||
|
=============
|
||||||
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).
|
||||||
|
|
||||||
@ -96,7 +24,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.
|
||||||
|
|
||||||
@ -104,7 +32,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~~ Debian 10 (Buster) 64-bit machine into a working mail server by installing and configuring various components.
|
Mail-in-a-Box turns a fresh Ubuntu 20.04 or 18.04 LTS 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."
|
||||||
|
|
||||||
@ -139,7 +67,7 @@ Clone this repository and checkout the tag corresponding to the most recent rele
|
|||||||
|
|
||||||
$ git clone https://github.com/mail-in-a-box/mailinabox
|
$ git clone https://github.com/mail-in-a-box/mailinabox
|
||||||
$ cd mailinabox
|
$ cd mailinabox
|
||||||
$ git checkout v0.52
|
$ git checkout v0.53
|
||||||
|
|
||||||
Begin the installation.
|
Begin the installation.
|
||||||
|
|
||||||
|
21
Vagrantfile
vendored
21
Vagrantfile
vendored
@ -1,20 +1,8 @@
|
|||||||
|
|
||||||
# -*- 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 = "debian/buster64"
|
config.vm.box = "ubuntu/bionic64"
|
||||||
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
|
||||||
@ -22,18 +10,17 @@ 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=192.168.50.4
|
export PUBLIC_IP=auto
|
||||||
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,123 +499,6 @@ 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:
|
||||||
@ -2576,19 +2459,6 @@ 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
|
||||||
@ -2791,52 +2661,6 @@ 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,24 +25,31 @@
|
|||||||
alias /var/lib/mailinabox/mta-sts.txt;
|
alias /var/lib/mailinabox/mta-sts.txt;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Z-Push (Microsoft Exchange ActiveSync)
|
# Roundcube Webmail configuration.
|
||||||
location /Microsoft-Server-ActiveSync {
|
rewrite ^/mail$ /mail/ redirect;
|
||||||
include /etc/nginx/fastcgi_params;
|
rewrite ^/mail/$ /mail/index.php;
|
||||||
fastcgi_param SCRIPT_FILENAME /usr/local/lib/z-push/index.php;
|
location /mail/ {
|
||||||
fastcgi_param PHP_VALUE "include_path=.:/usr/share/php:/usr/share/pear:/usr/share/awl/inc";
|
index index.php;
|
||||||
fastcgi_read_timeout 630;
|
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;
|
fastcgi_pass php-fpm;
|
||||||
|
|
||||||
# Outgoing mail also goes through this endpoint, so increase the maximum
|
# Outgoing mail also goes through this endpoint, so increase the maximum
|
||||||
# file upload limit to match the corresponding Postfix limit.
|
# file upload limit to match the corresponding Postfix limit.
|
||||||
client_max_body_size 128M;
|
client_max_body_size 128M;
|
||||||
}
|
}
|
||||||
location ~* ^/autodiscover/autodiscover.xml$ {
|
|
||||||
include fastcgi_params;
|
# ADDITIONAL DIRECTIVES HERE
|
||||||
fastcgi_param SCRIPT_FILENAME /usr/local/lib/z-push/autodiscover/autodiscover.php;
|
|
||||||
fastcgi_param PHP_VALUE "include_path=.:/usr/share/php:/usr/share/pear:/usr/share/awl/inc";
|
|
||||||
fastcgi_pass php-fpm;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 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
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# 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'.
|
||||||
@ -40,30 +38,6 @@
|
|||||||
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;
|
||||||
@ -122,3 +96,5 @@
|
|||||||
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
|
||||||
|
@ -1,15 +1,10 @@
|
|||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Redirecting...</title>
|
<title>this is a mail-in-a-box</title>
|
||||||
<meta name="robots" content="noindex">
|
<meta name="robots" content="noindex">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<h1>this is a mail-in-a-box</h1>
|
||||||
|
<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,7 +10,7 @@
|
|||||||
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, CannotAcquireLock
|
from exclusiveprocess import Lock
|
||||||
|
|
||||||
from utils import load_environment, shell, wait_for_service, fix_boto, get_php_version
|
from utils import load_environment, shell, wait_for_service, fix_boto, get_php_version
|
||||||
|
|
||||||
@ -210,21 +210,13 @@ def get_target_type(config):
|
|||||||
protocol = config["target"].split(":")[0]
|
protocol = config["target"].split(":")[0]
|
||||||
return protocol
|
return protocol
|
||||||
|
|
||||||
def perform_backup(full_backup, user_initiated=False):
|
def perform_backup(full_backup):
|
||||||
env = load_environment()
|
env = load_environment()
|
||||||
php_fpm = f"php{get_php_version()}-fpm"
|
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 once.
|
||||||
lock = Lock(name="mailinabox_backup_daemon", die=(not user_initiated))
|
Lock(die=True).forever()
|
||||||
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')
|
||||||
@ -329,11 +321,6 @@ def perform_backup(full_backup, user_initiated=False):
|
|||||||
# 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)
|
||||||
|
|
||||||
@ -346,7 +333,6 @@ def perform_backup(full_backup, user_initiated=False):
|
|||||||
['su', env['STORAGE_USER'], '-c', post_script, config["target"]],
|
['su', env['STORAGE_USER'], '-c', post_script, config["target"]],
|
||||||
env=env)
|
env=env)
|
||||||
|
|
||||||
|
|
||||||
def run_duplicity_verification():
|
def run_duplicity_verification():
|
||||||
env = load_environment()
|
env = load_environment()
|
||||||
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
|
backup_root = os.path.join(env["STORAGE_ROOT"], 'backup')
|
||||||
|
@ -280,17 +280,50 @@ def dns_set_secondary_nameserver():
|
|||||||
@app.route('/dns/custom')
|
@app.route('/dns/custom')
|
||||||
@authorized_personnel_only
|
@authorized_personnel_only
|
||||||
def dns_get_records(qname=None, rtype=None):
|
def dns_get_records(qname=None, rtype=None):
|
||||||
from dns_update import get_custom_dns_config
|
# Get the current set of custom DNS records.
|
||||||
return json_response([
|
from dns_update import get_custom_dns_config, get_dns_zones
|
||||||
|
records = get_custom_dns_config(env, only_real_records=True)
|
||||||
|
|
||||||
|
# Filter per the arguments for the more complex GET routes below.
|
||||||
|
records = [r for r in records
|
||||||
|
if (not qname or r[0] == qname)
|
||||||
|
and (not rtype or r[1] == rtype) ]
|
||||||
|
|
||||||
|
# Make a better data structure.
|
||||||
|
records = [
|
||||||
{
|
{
|
||||||
"qname": r[0],
|
"qname": r[0],
|
||||||
"rtype": r[1],
|
"rtype": r[1],
|
||||||
"value": r[2],
|
"value": r[2],
|
||||||
}
|
"sort-order": { },
|
||||||
for r in get_custom_dns_config(env)
|
} for r in records ]
|
||||||
if r[0] != "_secondary_nameserver"
|
|
||||||
and (not qname or r[0] == qname)
|
# To help with grouping by zone in qname sorting, label each record with which zone it is in.
|
||||||
and (not rtype or r[1] == rtype) ])
|
# There's an inconsistency in how we handle zones in get_dns_zones and in sort_domains, so
|
||||||
|
# do this first before sorting the domains within the zones.
|
||||||
|
zones = utils.sort_domains([z[0] for z in get_dns_zones(env)], env)
|
||||||
|
for r in records:
|
||||||
|
for z in zones:
|
||||||
|
if r["qname"] == z or r["qname"].endswith("." + z):
|
||||||
|
r["zone"] = z
|
||||||
|
break
|
||||||
|
|
||||||
|
# Add sorting information. The 'created' order follows the order in the YAML file on disk,
|
||||||
|
# which tracs the order entries were added in the control panel since we append to the end.
|
||||||
|
# The 'qname' sort order sorts by our standard domain name sort (by zone then by qname),
|
||||||
|
# then by rtype, and last by the original order in the YAML file (since sorting by value
|
||||||
|
# may not make sense, unless we parse IP addresses, for example).
|
||||||
|
for i, r in enumerate(records):
|
||||||
|
r["sort-order"]["created"] = i
|
||||||
|
domain_sort_order = utils.sort_domains([r["qname"] for r in records], env)
|
||||||
|
for i, r in enumerate(sorted(records, key = lambda r : (
|
||||||
|
zones.index(r["zone"]),
|
||||||
|
domain_sort_order.index(r["qname"]),
|
||||||
|
r["rtype"]))):
|
||||||
|
r["sort-order"]["qname"] = i
|
||||||
|
|
||||||
|
# Return.
|
||||||
|
return json_response(records)
|
||||||
|
|
||||||
@app.route('/dns/custom/<qname>', methods=['GET', 'POST', 'PUT', 'DELETE'])
|
@app.route('/dns/custom/<qname>', methods=['GET', 'POST', 'PUT', 'DELETE'])
|
||||||
@app.route('/dns/custom/<qname>/<rtype>', methods=['GET', 'POST', 'PUT', 'DELETE'])
|
@app.route('/dns/custom/<qname>/<rtype>', methods=['GET', 'POST', 'PUT', 'DELETE'])
|
||||||
@ -594,19 +627,6 @@ 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():
|
||||||
@ -621,49 +641,6 @@ 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/')
|
||||||
|
@ -753,7 +753,7 @@ def write_opendkim_tables(domains, env):
|
|||||||
|
|
||||||
########################################################################
|
########################################################################
|
||||||
|
|
||||||
def get_custom_dns_config(env):
|
def get_custom_dns_config(env, only_real_records=False):
|
||||||
try:
|
try:
|
||||||
custom_dns = rtyaml.load(open(os.path.join(env['STORAGE_ROOT'], 'dns/custom.yaml')))
|
custom_dns = rtyaml.load(open(os.path.join(env['STORAGE_ROOT'], 'dns/custom.yaml')))
|
||||||
if not isinstance(custom_dns, dict): raise ValueError() # caught below
|
if not isinstance(custom_dns, dict): raise ValueError() # caught below
|
||||||
@ -761,6 +761,8 @@ def get_custom_dns_config(env):
|
|||||||
return [ ]
|
return [ ]
|
||||||
|
|
||||||
for qname, value in custom_dns.items():
|
for qname, value in custom_dns.items():
|
||||||
|
if qname == "_secondary_nameserver" and only_real_records: continue # skip fake record
|
||||||
|
|
||||||
# Short form. Mapping a domain name to a string is short-hand
|
# Short form. Mapping a domain name to a string is short-hand
|
||||||
# for creating A records.
|
# for creating A records.
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
|
@ -44,9 +44,8 @@ TIME_DELTAS = OrderedDict([
|
|||||||
('today', datetime.datetime.now() - datetime.datetime.now().replace(hour=0, minute=0, second=0))
|
('today', datetime.datetime.now() - datetime.datetime.now().replace(hour=0, minute=0, second=0))
|
||||||
])
|
])
|
||||||
|
|
||||||
# Start date > end date!
|
END_DATE = NOW = datetime.datetime.now()
|
||||||
START_DATE = datetime.datetime.now()
|
START_DATE = None
|
||||||
END_DATE = None
|
|
||||||
|
|
||||||
VERBOSE = False
|
VERBOSE = False
|
||||||
|
|
||||||
@ -121,7 +120,7 @@ def scan_mail_log(env):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
print("Scanning logs from {:%Y-%m-%d %H:%M:%S} to {:%Y-%m-%d %H:%M:%S}".format(
|
print("Scanning logs from {:%Y-%m-%d %H:%M:%S} to {:%Y-%m-%d %H:%M:%S}".format(
|
||||||
END_DATE, START_DATE)
|
START_DATE, END_DATE)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Scan the lines in the log files until the date goes out of range
|
# Scan the lines in the log files until the date goes out of range
|
||||||
@ -253,7 +252,7 @@ def scan_mail_log(env):
|
|||||||
|
|
||||||
if collector["postgrey"]:
|
if collector["postgrey"]:
|
||||||
msg = "Greylisted Email {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}"
|
msg = "Greylisted Email {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}"
|
||||||
print_header(msg.format(END_DATE, START_DATE))
|
print_header(msg.format(START_DATE, END_DATE))
|
||||||
|
|
||||||
print(textwrap.fill(
|
print(textwrap.fill(
|
||||||
"The following mail was greylisted, meaning the emails were temporarily rejected. "
|
"The following mail was greylisted, meaning the emails were temporarily rejected. "
|
||||||
@ -291,7 +290,7 @@ def scan_mail_log(env):
|
|||||||
|
|
||||||
if collector["rejected"]:
|
if collector["rejected"]:
|
||||||
msg = "Blocked Email {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}"
|
msg = "Blocked Email {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}"
|
||||||
print_header(msg.format(END_DATE, START_DATE))
|
print_header(msg.format(START_DATE, END_DATE))
|
||||||
|
|
||||||
data = OrderedDict(sorted(collector["rejected"].items(), key=email_sort))
|
data = OrderedDict(sorted(collector["rejected"].items(), key=email_sort))
|
||||||
|
|
||||||
@ -345,19 +344,19 @@ def scan_mail_log_line(line, collector):
|
|||||||
# Replaced the dateutil parser for a less clever way of parser that is roughly 4 times faster.
|
# Replaced the dateutil parser for a less clever way of parser that is roughly 4 times faster.
|
||||||
# date = dateutil.parser.parse(date)
|
# date = dateutil.parser.parse(date)
|
||||||
|
|
||||||
# date = datetime.datetime.strptime(date, '%b %d %H:%M:%S')
|
# strptime fails on Feb 29 with ValueError: day is out of range for month if correct year is not provided.
|
||||||
# date = date.replace(START_DATE.year)
|
# See https://bugs.python.org/issue26460
|
||||||
|
date = datetime.datetime.strptime(str(NOW.year) + ' ' + date, '%Y %b %d %H:%M:%S')
|
||||||
# strptime fails on Feb 29 if correct year is not provided. See https://bugs.python.org/issue26460
|
# if log date in future, step back a year
|
||||||
date = datetime.datetime.strptime(str(START_DATE.year) + ' ' + date, '%Y %b %d %H:%M:%S')
|
if date > NOW:
|
||||||
|
date = date.replace(year = NOW.year - 1)
|
||||||
#print("date:", date)
|
#print("date:", date)
|
||||||
|
|
||||||
# Check if the found date is within the time span we are scanning
|
# Check if the found date is within the time span we are scanning
|
||||||
# END_DATE < START_DATE
|
if date > END_DATE:
|
||||||
if date > START_DATE:
|
|
||||||
# Don't process, and halt
|
# Don't process, and halt
|
||||||
return False
|
return False
|
||||||
elif date < END_DATE:
|
elif date < START_DATE:
|
||||||
# Don't process, but continue
|
# Don't process, but continue
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -606,7 +605,7 @@ def email_sort(email):
|
|||||||
|
|
||||||
|
|
||||||
def valid_date(string):
|
def valid_date(string):
|
||||||
""" Validate the given date string fetched from the --startdate argument """
|
""" Validate the given date string fetched from the --enddate argument """
|
||||||
try:
|
try:
|
||||||
date = dateutil.parser.parse(string)
|
date = dateutil.parser.parse(string)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -820,12 +819,14 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
parser.add_argument("-t", "--timespan", choices=TIME_DELTAS.keys(), default='today',
|
parser.add_argument("-t", "--timespan", choices=TIME_DELTAS.keys(), default='today',
|
||||||
metavar='<time span>',
|
metavar='<time span>',
|
||||||
help="Time span to scan, going back from the start date. Possible values: "
|
help="Time span to scan, going back from the end date. Possible values: "
|
||||||
"{}. Defaults to 'today'.".format(", ".join(list(TIME_DELTAS.keys()))))
|
"{}. Defaults to 'today'.".format(", ".join(list(TIME_DELTAS.keys()))))
|
||||||
parser.add_argument("-d", "--startdate", action="store", dest="startdate",
|
# keep the --startdate arg for backward compatibility
|
||||||
type=valid_date, metavar='<start date>',
|
parser.add_argument("-d", "--enddate", "--startdate", action="store", dest="enddate",
|
||||||
help="Date and time to start scanning the log file from. If no date is "
|
type=valid_date, metavar='<end date>',
|
||||||
"provided, scanning will start from the current date and time.")
|
help="Date and time to end scanning the log file. If no date is "
|
||||||
|
"provided, scanning will end at the current date and time. "
|
||||||
|
"Alias --startdate is for compatibility.")
|
||||||
parser.add_argument("-u", "--users", action="store", dest="users",
|
parser.add_argument("-u", "--users", action="store", dest="users",
|
||||||
metavar='<email1,email2,email...>',
|
metavar='<email1,email2,email...>',
|
||||||
help="Comma separated list of (partial) email addresses to filter the "
|
help="Comma separated list of (partial) email addresses to filter the "
|
||||||
@ -837,13 +838,13 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.startdate is not None:
|
if args.enddate is not None:
|
||||||
START_DATE = args.startdate
|
END_DATE = args.enddate
|
||||||
if args.timespan == 'today':
|
if args.timespan == 'today':
|
||||||
args.timespan = 'day'
|
args.timespan = 'day'
|
||||||
print("Setting start date to {}".format(START_DATE))
|
print("Setting end date to {}".format(END_DATE))
|
||||||
|
|
||||||
END_DATE = START_DATE - TIME_DELTAS[args.timespan]
|
START_DATE = END_DATE - TIME_DELTAS[args.timespan]
|
||||||
|
|
||||||
VERBOSE = args.verbose
|
VERBOSE = args.verbose
|
||||||
|
|
||||||
|
@ -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_warning("""Outbound mail (SMTP port 25) seems to be blocked by your network. You
|
output.print_error("""Outbound mail (SMTP port 25) seems to be blocked by your network. You
|
||||||
will not be able to send any mail without a SMTP relay. Many residential networks block port 25 to prevent
|
will not be able to send any mail. Many residential networks block port 25 to prevent hijacked
|
||||||
hijacked machines from being able to send spam. A quick connection test to Google's mail server on port 25
|
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,19 +300,6 @@ 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)
|
||||||
|
@ -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="fas fa-pen"></span>
|
<span class="glyphicon glyphicon-pencil"></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="fas fa-trash"></span>
|
<span class="glyphicon glyphicon-trash"></span>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class='address'> </td>
|
<td class='address'> </td>
|
||||||
@ -153,8 +153,8 @@ function show_aliases() {
|
|||||||
function(r) {
|
function(r) {
|
||||||
$('#alias_table tbody').html("");
|
$('#alias_table tbody').html("");
|
||||||
for (var i = 0; i < r.length; i++) {
|
for (var i = 0; i < r.length; i++) {
|
||||||
var hdr = $("<tr><td colspan='3'><h4/></td></tr>");
|
var hdr = $("<tr><th colspan='4' style='background-color: #EEE'></th></tr>");
|
||||||
hdr.find('h4').text(r[i].domain);
|
hdr.find('th').text(r[i].domain);
|
||||||
$('#alias_table tbody').append(hdr);
|
$('#alias_table tbody').append(hdr);
|
||||||
|
|
||||||
for (var k = 0; k < r[i].aliases.length; k++) {
|
for (var k = 0; k < r[i].aliases.length; k++) {
|
||||||
|
@ -57,7 +57,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<table id="custom-dns-current" class="table" style="width: auto; display: none">
|
<div style="text-align: right; font-size; 90%; margin-top: 1em;">
|
||||||
|
sort by:
|
||||||
|
<a href="#" onclick="window.miab_custom_dns_data_sort_order='qname'; show_current_custom_dns_update_after_sort(); return false;">domain name</a>
|
||||||
|
|
|
||||||
|
<a href="#" onclick="window.miab_custom_dns_data_sort_order='created'; show_current_custom_dns_update_after_sort(); return false;">created</a>
|
||||||
|
</div>
|
||||||
|
<table id="custom-dns-current" class="table" style="width: auto; display: none; margin-top: 0;">
|
||||||
<thead>
|
<thead>
|
||||||
<th>Domain Name</th>
|
<th>Domain Name</th>
|
||||||
<th>Record Type</th>
|
<th>Record Type</th>
|
||||||
@ -192,36 +198,38 @@ function show_current_custom_dns() {
|
|||||||
$('#custom-dns-current').fadeIn();
|
$('#custom-dns-current').fadeIn();
|
||||||
else
|
else
|
||||||
$('#custom-dns-current').fadeOut();
|
$('#custom-dns-current').fadeOut();
|
||||||
|
window.miab_custom_dns_data = data;
|
||||||
var reverse_fqdn = function(el) {
|
show_current_custom_dns_update_after_sort();
|
||||||
el.qname = el.qname.split('.').reverse().join('.');
|
});
|
||||||
return el;
|
|
||||||
}
|
|
||||||
var sort = function(a, b) {
|
|
||||||
if(a.qname === b.qname) {
|
|
||||||
if(a.rtype === b.rtype) {
|
|
||||||
return a.value > b.value ? 1 : -1;
|
|
||||||
}
|
|
||||||
return a.rtype > b.rtype ? 1 : -1;
|
|
||||||
}
|
|
||||||
return a.qname > b.qname ? 1 : -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data = data.map(reverse_fqdn).sort(sort).map(reverse_fqdn);
|
function show_current_custom_dns_update_after_sort() {
|
||||||
|
var data = window.miab_custom_dns_data;
|
||||||
|
var sort_key = window.miab_custom_dns_data_sort_order || "qname";
|
||||||
|
|
||||||
$('#custom-dns-current').find("tbody").text('');
|
data.sort(function(a, b) { return a["sort-order"][sort_key] - b["sort-order"][sort_key] });
|
||||||
|
|
||||||
|
var tbody = $('#custom-dns-current').find("tbody");
|
||||||
|
tbody.text('');
|
||||||
|
var last_zone = null;
|
||||||
for (var i = 0; i < data.length; i++) {
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
if (sort_key == "qname" && data[i].zone != last_zone) {
|
||||||
|
var r = $("<tr><th colspan=4 style='background-color: #EEE'></th></tr>");
|
||||||
|
r.find("th").text(data[i].zone);
|
||||||
|
tbody.append(r);
|
||||||
|
last_zone = data[i].zone;
|
||||||
|
}
|
||||||
|
|
||||||
var tr = $("<tr/>");
|
var tr = $("<tr/>");
|
||||||
$('#custom-dns-current').find("tbody").append(tr);
|
tbody.append(tr);
|
||||||
tr.attr('data-qname', data[i].qname);
|
tr.attr('data-qname', data[i].qname);
|
||||||
tr.attr('data-rtype', data[i].rtype);
|
tr.attr('data-rtype', data[i].rtype);
|
||||||
tr.attr('data-value', data[i].value);
|
tr.attr('data-value', data[i].value);
|
||||||
tr.append($('<td class="long"/>').text(data[i].qname));
|
tr.append($('<td class="long"/>').text(data[i].qname));
|
||||||
tr.append($('<td/>').text(data[i].rtype));
|
tr.append($('<td/>').text(data[i].rtype));
|
||||||
tr.append($('<td class="long"/>').text(data[i].value));
|
tr.append($('<td class="long" style="max-width: 40em"/>').text(data[i].value));
|
||||||
tr.append($('<td>[<a href="#" onclick="return delete_custom_dns_record(this)">delete</a>]</td>'));
|
tr.append($('<td>[<a href="#" onclick="return delete_custom_dns_record(this)">delete</a>]</td>'));
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function delete_custom_dns_record(elem) {
|
function delete_custom_dns_record(elem) {
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<p class="alert" role="alert">
|
<p class="alert" role="alert">
|
||||||
<span class="fas fa-info-circle"></span>
|
<span class="glyphicon glyphicon-info-sign"></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" style="margin-left: 20px" class="btn btn-primary">Download</button>
|
<button type="submit" class="btn btn-primary">Download</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -10,9 +10,6 @@
|
|||||||
<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;
|
||||||
@ -66,6 +63,7 @@
|
|||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<link rel="stylesheet" href="/admin/assets/bootstrap/css/bootstrap-theme.min.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@ -73,52 +71,53 @@
|
|||||||
<!--[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 bg-light">
|
<div class="container">
|
||||||
<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 navbar-expand-lg">
|
<div class="navbar-collapse collapse">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
<li class="btn dropdown">
|
<li class="dropdown">
|
||||||
<a style="color: black;" href="#" class="dropdown-toggle" data-toggle="dropdown">System <b class="caret"></b></a>
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">System <b class="caret"></b></a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li class="dropdown-item"><a href="#system_status" onclick="return show_panel(this);">Status Checks</a></li>
|
<li><a href="#system_status" onclick="return show_panel(this);">Status Checks</a></li>
|
||||||
<li class="dropdown-item"><a href="#tls" onclick="return show_panel(this);">TLS (SSL) Certificates</a></li>
|
<li><a href="#tls" onclick="return show_panel(this);">TLS (SSL) Certificates</a></li>
|
||||||
<li class="dropdown-item"><a href="#system_backup" onclick="return show_panel(this);">Backup Status</a></li>
|
<li><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 class="dropdown-item"><a href="#custom_dns" onclick="return show_panel(this);">Custom DNS</a></li>
|
<li><a href="#custom_dns" onclick="return show_panel(this);">Custom DNS</a></li>
|
||||||
<li class="dropdown-item"><a href="#external_dns" onclick="return show_panel(this);">External DNS</a></li>
|
<li><a href="#external_dns" onclick="return show_panel(this);">External DNS</a></li>
|
||||||
<li class="dropdown-item"><a href="/admin/munin" target="_blank">Munin Monitoring</a></li>
|
<li><a href="/admin/munin" target="_blank">Munin Monitoring</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="btn dropdown">
|
<li class="dropdown">
|
||||||
<a style="color: black;" href="#" class="dropdown-toggle" data-toggle="dropdown">Mail & Users <b class="caret"></b></a>
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Mail & Users <b class="caret"></b></a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li class="dropdown-item"><a href="#mail-guide" onclick="return show_panel(this);">Instructions</a></li>
|
<li><a href="#mail-guide" onclick="return show_panel(this);">Instructions</a></li>
|
||||||
<li class="dropdown-item"><a href="#users" onclick="return show_panel(this);">Users</a></li>
|
<li><a href="#users" onclick="return show_panel(this);">Users</a></li>
|
||||||
<li class="dropdown-item"><a href="#aliases" onclick="return show_panel(this);">Aliases</a></li>
|
<li><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 class="dropdown-item"><a href="#mfa" onclick="return show_panel(this);">Two-Factor Authentication</a></li>
|
<li><a href="#mfa" onclick="return show_panel(this);">Two-Factor Authentication</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="btn"><a style="color: black;" href="#sync_guide" onclick="return show_panel(this);">Contacts/Calendar</a></li>
|
<li><a href="#sync_guide" onclick="return show_panel(this);">Contacts/Calendar</a></li>
|
||||||
<li class="btn"><a style="color: black;" href="#web" onclick="return show_panel(this);">Web</a></li>
|
<li><a href="#web" onclick="return show_panel(this);">Web</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="btn nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
<li><a href="#" onclick="do_logout(); return false;" style="color: black; font-weight: bold;">Log out</a></li>
|
<li><a href="#" onclick="do_logout(); return false;" style="color: white">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>
|
||||||
@ -170,7 +169,7 @@
|
|||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>This is a <a href="https://github.com/ddavness/power-mailinabox">Power Mail-in-a-Box</a> - {{distname}}</p>
|
<p>This is a <a href="https://mailinabox.email">Mail-in-a-Box</a>.</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div> <!-- /container -->
|
</div> <!-- /container -->
|
||||||
|
|
||||||
@ -185,8 +184,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">
|
||||||
<h4 class="modal-title" id="errorModalTitle"> </h4>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h4 class="modal-title" id="errorModalTitle"> </h4>
|
||||||
</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-12">
|
<div class="col-sm-9">
|
||||||
<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-12">
|
<div class="col-sm-9">
|
||||||
<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-12">
|
<div class="col-sm-9">
|
||||||
<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="card">
|
<div class="panel panel-danger">
|
||||||
<div class="card-header text-white bg-danger">
|
<div class="panel-heading">
|
||||||
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="card-body bg-light">
|
<div class="panel-body">
|
||||||
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 btn-primary">Enable Two-Factor Authentication</button>
|
<button id="totp-setup-submit" disabled type="submit" class="btn">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="card bg-light">
|
<div id="output-2fa" class="panel panel-danger">
|
||||||
<div class="card-body"></div>
|
<div class="panel-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('.card-body').innerHTML = '';
|
el.output.querySelector('.panel-body').innerHTML = '';
|
||||||
el.output.classList.remove('visible');
|
el.output.classList.remove('visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
function render_error(msg) {
|
function render_error(msg) {
|
||||||
el.output.querySelector('.card-body').innerHTML = msg;
|
el.output.querySelector('.panel-body').innerHTML = msg;
|
||||||
el.output.classList.add('visible');
|
el.output.classList.add('visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,135 +0,0 @@
|
|||||||
<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,7 +12,6 @@
|
|||||||
|
|
||||||
<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,12 +12,13 @@
|
|||||||
<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-3">
|
<div class="col-sm-2">
|
||||||
<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>
|
||||||
<option value="rsync">rsync</option>
|
<option value="rsync">rsync</option>
|
||||||
<option value="s3">Amazon S3</option>
|
<option value="s3">Amazon S3</option>
|
||||||
|
<option value="b2">Backblaze B2</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -165,11 +166,6 @@
|
|||||||
<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() {
|
||||||
@ -216,17 +212,12 @@ 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/>');
|
||||||
@ -352,29 +343,4 @@ 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>
|
@ -6,36 +6,29 @@
|
|||||||
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 {
|
#system-checks .status-error td {
|
||||||
color: rgb(140, 0, 0);
|
color: #733;
|
||||||
}
|
}
|
||||||
|
|
||||||
#system-checks .status-warning td {
|
#system-checks .status-warning td {
|
||||||
color: rgb(170, 120, 0);
|
color: #770;
|
||||||
}
|
}
|
||||||
|
|
||||||
#system-checks .status-ok td {
|
#system-checks .status-ok td {
|
||||||
color: rgb(0, 140, 0);
|
color: #040;
|
||||||
}
|
}
|
||||||
|
|
||||||
#system-checks div.extra {
|
#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;
|
||||||
@ -43,8 +36,8 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div>
|
<div class="row">
|
||||||
<div>
|
<div class="col-md-push-9 col-md-3">
|
||||||
|
|
||||||
<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>
|
||||||
@ -52,19 +45,18 @@
|
|||||||
</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>
|
<div><a onclick="return enable_privacy(!current_privacy_setting)" href="#"><span>Enable/Disable</span> New-Version Check</a></div>
|
||||||
New-Version Check</a></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>
|
||||||
<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>
|
||||||
|
|
||||||
</div> <!-- /col -->
|
</div> <!-- /col -->
|
||||||
<br>
|
<div class="col-md-pull-3 col-md-8">
|
||||||
<div>
|
|
||||||
|
|
||||||
<table id="system-checks" class="table">
|
<table id="system-checks" class="table" style="max-width: 60em">
|
||||||
<thead></thead>
|
<thead>
|
||||||
<tbody></tbody>
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</div> <!-- /col -->
|
</div> <!-- /col -->
|
||||||
@ -72,7 +64,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
function show_system_status() {
|
function show_system_status() {
|
||||||
$('#system-checks tbody').html("<tr><td class='text-muted'>Loading...</td></tr>")
|
$('#system-checks tbody').html("<tr><td colspan='2' class='text-muted'>Loading...</td></tr>")
|
||||||
|
|
||||||
api(
|
api(
|
||||||
"/system/privacy",
|
"/system/privacy",
|
||||||
@ -102,20 +94,20 @@
|
|||||||
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'/><p class='showhide btn btn-light' href='#'/><div class='extra'></div></tr>");
|
var n = $("<tr><td class='status'/><td class='message'><p style='margin: 0'/><div class='extra'/><a class='showhide' href='#'/></tr>");
|
||||||
if (i == 0) n.addClass('first')
|
if (i == 0) n.addClass('first')
|
||||||
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('.showhide').show().text("Show More").click(function () {
|
n.find('a.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;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<h2>Users</h2>
|
<h2>Users</h2>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#user_table h4 { margin: 1em 0 0 0; }
|
|
||||||
#user_table tr.account_inactive td.address { color: #888; text-decoration: line-through; }
|
#user_table tr.account_inactive td.address { color: #888; text-decoration: line-through; }
|
||||||
#user_table .actions { margin-top: .33em; font-size: 95%; }
|
#user_table .actions { margin-top: .33em; font-size: 95%; }
|
||||||
#user_table .account_inactive .if_active { display: none; }
|
#user_table .account_inactive .if_active { display: none; }
|
||||||
@ -134,8 +133,8 @@ function show_users() {
|
|||||||
function(r) {
|
function(r) {
|
||||||
$('#user_table tbody').html("");
|
$('#user_table tbody').html("");
|
||||||
for (var i = 0; i < r.length; i++) {
|
for (var i = 0; i < r.length; i++) {
|
||||||
var hdr = $("<tr><td colspan='3'><h4/></td></tr>");
|
var hdr = $("<tr><th colspan='2' style='background-color: #EEE'></th></tr>");
|
||||||
hdr.find('h4').text(r[i].domain);
|
hdr.find('th').text(r[i].domain);
|
||||||
$('#user_table tbody').append(hdr);
|
$('#user_table tbody').append(hdr);
|
||||||
|
|
||||||
for (var k = 0; k < r[i].users.length; k++) {
|
for (var k = 0; k < r[i].users.length; k++) {
|
||||||
|
@ -206,16 +206,8 @@ 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 not os.path.exists(nginx_conf_custom_include):
|
if 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
|
||||||
|
@ -2,17 +2,43 @@
|
|||||||
#########################################################
|
#########################################################
|
||||||
# This script is intended to be run like this:
|
# This script is intended to be run like this:
|
||||||
#
|
#
|
||||||
# curl https://dvn.pt/power-miab | sudo bash
|
# curl https://mailinabox.email/setup.sh | sudo bash
|
||||||
#
|
#
|
||||||
#########################################################
|
#########################################################
|
||||||
|
|
||||||
if [ -z "$TAG" ]; then
|
if [ -z "$TAG" ]; then
|
||||||
# Make s
|
# If a version to install isn't explicitly given as an environment
|
||||||
OS=`lsb_release -d | sed 's/.*:\s*//'`
|
# variable, then install the latest version. But the latest version
|
||||||
if [ "$OS" == "Debian GNU/Linux 10 (buster)" -o "$(echo $OS | grep -o 'Ubuntu 20.04')" == "Ubuntu 20.04" ]; then
|
# depends on the operating system. Existing Ubuntu 14.04 users need
|
||||||
TAG=v0.52.POWER.0
|
# to be able to upgrade to the latest version supporting Ubuntu 14.04,
|
||||||
|
# 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/20\.04\.[0-9]/20.04/' `" == "Ubuntu 20.04 LTS" ]; then
|
||||||
|
# This machine is running Ubuntu 20.04.
|
||||||
|
TAG=v0.53
|
||||||
|
|
||||||
|
elif [ "`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.53
|
||||||
|
|
||||||
|
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 Debian 10 OR Ubuntu 20.04 LTS."
|
echo "This script must be run on a system running Ubuntu 20.04, 18.04 or 14.04."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
@ -62,7 +62,8 @@ chmod go-rwx $STORAGE_ROOT/mail/dkim
|
|||||||
|
|
||||||
tools/editconf.py /etc/opendmarc.conf -s \
|
tools/editconf.py /etc/opendmarc.conf -s \
|
||||||
"Syslog=true" \
|
"Syslog=true" \
|
||||||
"Socket=inet:8893@[127.0.0.1]"
|
"Socket=inet:8893@[127.0.0.1]" \
|
||||||
|
"FailureReports=true"
|
||||||
|
|
||||||
# SPFIgnoreResults causes the filter to ignore any SPF results in the header
|
# SPFIgnoreResults causes the filter to ignore any SPF results in the header
|
||||||
# of the message. This is useful if you want the filter to perfrom SPF checks
|
# of the message. This is useful if you want the filter to perfrom SPF checks
|
||||||
@ -81,6 +82,12 @@ tools/editconf.py /etc/opendmarc.conf -s \
|
|||||||
tools/editconf.py /etc/opendmarc.conf -s \
|
tools/editconf.py /etc/opendmarc.conf -s \
|
||||||
"SPFSelfValidate=true"
|
"SPFSelfValidate=true"
|
||||||
|
|
||||||
|
# Enables generation of failure reports for sending domains that publish a
|
||||||
|
# "none" policy.
|
||||||
|
|
||||||
|
tools/editconf.py /etc/opendmarc.conf -s \
|
||||||
|
"FailureReportsOnNone=true"
|
||||||
|
|
||||||
# AlwaysAddARHeader Adds an "Authentication-Results:" header field even to
|
# AlwaysAddARHeader Adds an "Authentication-Results:" header field even to
|
||||||
# unsigned messages from domains with no "signs all" policy. The reported DKIM
|
# unsigned messages from domains with no "signs all" policy. The reported DKIM
|
||||||
# result will be "none" in such cases. Normally unsigned mail from non-strict
|
# result will be "none" in such cases. Normally unsigned mail from non-strict
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
source setup/functions.sh
|
source setup/functions.sh
|
||||||
|
|
||||||
echo Installing geoip packages...
|
echo Installing geoip packages...
|
||||||
@ -8,8 +9,8 @@ echo Installing geoip packages...
|
|||||||
gunzip -c tools/goiplookup.gz > /usr/local/bin/goiplookup
|
gunzip -c tools/goiplookup.gz > /usr/local/bin/goiplookup
|
||||||
chmod +x /usr/local/bin/goiplookup
|
chmod +x /usr/local/bin/goiplookup
|
||||||
|
|
||||||
# check that geoipdb is older then 2 months, to not hit the server too often
|
# check that GeoLite2-Country.mmdb is older then 2 months, to not hit the server too often
|
||||||
if [[ ! -d /usr/share/GeoIP || ! -f /usr/share/GeoIP/GeoIP.dat || $(find "/usr/share/GeoIP/GeoIP.dat" -mtime +60 -print) ]]; then
|
if [[ ! -d /usr/share/GeoIP || ! -f /usr/share/GeoIP/GeoLite2-Country.mmdb || $(find "/usr/share/GeoIP/GeoLite2-Country.mmdb" -mtime +60 -print) ]]; then
|
||||||
echo updating goiplookup database
|
echo updating goiplookup database
|
||||||
goiplookup db-update
|
goiplookup db-update
|
||||||
else
|
else
|
||||||
@ -48,6 +49,10 @@ fi
|
|||||||
|
|
||||||
## Install geo ip lookup files
|
## Install geo ip lookup files
|
||||||
|
|
||||||
|
# check that GeoIP.dat is older then 2 months, to not hit the server too often
|
||||||
|
if [[ ! -d /usr/share/GeoIP || ! -f /usr/share/GeoIP/GeoIP.dat || $(find "/usr/share/GeoIP/GeoIP.dat" -mtime +60 -print) ]]; then
|
||||||
|
echo updating GeoIP database
|
||||||
|
|
||||||
# Move old file away if it exists
|
# Move old file away if it exists
|
||||||
if [ -f "/usr/share/GeoIP/GeoIP.dat" ]; then
|
if [ -f "/usr/share/GeoIP/GeoIP.dat" ]; then
|
||||||
mv -f /usr/share/GeoIP/GeoIP.dat /usr/share/GeoIP/GeoIP.dat.bak
|
mv -f /usr/share/GeoIP/GeoIP.dat /usr/share/GeoIP/GeoIP.dat.bak
|
||||||
@ -57,6 +62,7 @@ hide_output wget -P /usr/share/GeoIP/ https://dl.miyuru.lk/geoip/maxmind/country
|
|||||||
|
|
||||||
if [ -f "/usr/share/GeoIP/maxmind.dat.gz" ]; then
|
if [ -f "/usr/share/GeoIP/maxmind.dat.gz" ]; then
|
||||||
gunzip -c /usr/share/GeoIP/maxmind.dat.gz > /usr/share/GeoIP/GeoIP.dat
|
gunzip -c /usr/share/GeoIP/maxmind.dat.gz > /usr/share/GeoIP/GeoIP.dat
|
||||||
|
rm -f /usr/share/GeoIP/maxmind.dat.gz
|
||||||
else
|
else
|
||||||
echo Did not correctly download maxmind geoip country database
|
echo Did not correctly download maxmind geoip country database
|
||||||
fi
|
fi
|
||||||
@ -79,6 +85,7 @@ hide_output wget -P /usr/share/GeoIP/ https://dl.miyuru.lk/geoip/maxmind/city/ma
|
|||||||
|
|
||||||
if [ -f "/usr/share/GeoIP/maxmind.dat.gz" ]; then
|
if [ -f "/usr/share/GeoIP/maxmind.dat.gz" ]; then
|
||||||
gunzip -c /usr/share/GeoIP/maxmind.dat.gz > /usr/share/GeoIP/GeoIPCity.dat
|
gunzip -c /usr/share/GeoIP/maxmind.dat.gz > /usr/share/GeoIP/GeoIPCity.dat
|
||||||
|
rm -f /usr/share/GeoIP/maxmind.dat.gz
|
||||||
else
|
else
|
||||||
echo Did not correctly download maxmind geoip city database
|
echo Did not correctly download maxmind geoip city database
|
||||||
fi
|
fi
|
||||||
@ -91,4 +98,7 @@ if [ ! -f "/usr/share/GeoIP/GeoIPCity.dat" ]; then
|
|||||||
mv /usr/share/GeoIP/GeoIPCity.dat.bak /usr/share/GeoIP/GeoIPCity.dat
|
mv /usr/share/GeoIP/GeoIPCity.dat.bak /usr/share/GeoIP/GeoIPCity.dat
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
echo skipping GeoIP database update
|
||||||
|
fi
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ tools/editconf.py /etc/postfix/main.cf \
|
|||||||
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 Power Mail-in-a-Box (Debian/Postfix)" \
|
smtpd_banner="\$myhostname ESMTP Hi, I'm a Mail-in-a-Box (Ubuntu/Postfix; see https://mailinabox.email/)" \
|
||||||
mydestination=localhost
|
mydestination=localhost
|
||||||
|
|
||||||
# Tweak some queue settings:
|
# Tweak some queue settings:
|
||||||
@ -260,19 +260,6 @@ chmod +x /etc/cron.daily/mailinabox-postgrey-whitelist
|
|||||||
tools/editconf.py /etc/postfix/main.cf \
|
tools/editconf.py /etc/postfix/main.cf \
|
||||||
message_size_limit=134217728
|
message_size_limit=134217728
|
||||||
|
|
||||||
# Store default configurations for SMTP relays:
|
|
||||||
tools/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
|
||||||
|
@ -27,9 +27,10 @@ done
|
|||||||
# provision free TLS certificates.
|
# provision free TLS certificates.
|
||||||
apt_install duplicity python3-pip virtualenv certbot
|
apt_install duplicity python3-pip virtualenv certbot
|
||||||
|
|
||||||
|
# b2sdk is used for backblaze backups.
|
||||||
# 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
|
||||||
hide_output pip3 install --upgrade boto
|
hide_output pip3 install --upgrade b2sdk boto
|
||||||
|
|
||||||
# Create a virtualenv for the installation of Python 3 packages
|
# Create a virtualenv for the installation of Python 3 packages
|
||||||
# used by the management daemon.
|
# used by the management daemon.
|
||||||
@ -50,7 +51,7 @@ hide_output $venv/bin/pip install --upgrade \
|
|||||||
rtyaml "email_validator>=1.0.0" "exclusiveprocess" \
|
rtyaml "email_validator>=1.0.0" "exclusiveprocess" \
|
||||||
flask dnspython python-dateutil \
|
flask dnspython python-dateutil \
|
||||||
qrcode[pil] pyotp \
|
qrcode[pil] pyotp \
|
||||||
"idna>=2.0.0" "cryptography==2.2.2" boto psutil postfix-mta-sts-resolver
|
"idna>=2.0.0" "cryptography==2.2.2" boto psutil postfix-mta-sts-resolver b2sdk
|
||||||
|
|
||||||
# CONFIGURATION
|
# CONFIGURATION
|
||||||
|
|
||||||
@ -69,32 +70,22 @@ rm -rf $assets_dir
|
|||||||
mkdir -p $assets_dir
|
mkdir -p $assets_dir
|
||||||
|
|
||||||
# jQuery CDN URL
|
# jQuery CDN URL
|
||||||
jquery_version=3.5.1
|
jquery_version=2.1.4
|
||||||
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 c8e1c8b386dc5b7a9184c763c88d19a346eb3342 $assets_dir/jquery.min.js
|
wget_verify $jquery_url/jquery-$jquery_version.min.js 43dc554608df885a59ddeece1598c6ace434d747 $assets_dir/jquery.min.js
|
||||||
|
|
||||||
# Bootstrap CDN URL
|
# Bootstrap CDN URL
|
||||||
bootstrap_version=4.6.0
|
bootstrap_version=3.3.7
|
||||||
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 a1d385dc33cb415512d2f38215a554c4380dac2d /tmp/bootstrap.zip
|
wget_verify $bootstrap_url e6b1000b94e835ffd37f4c6dcbdad43f4b48a02a /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;
|
||||||
@ -126,14 +117,3 @@ 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
|
|
||||||
|
@ -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 (Power) Mail-in-a-Box!
|
"Hello and thanks for deploying a 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 Debian/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 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.
|
||||||
@ -124,6 +124,7 @@ if [ -z "${ADMIN_HOME_IP:-}" ]; then
|
|||||||
if [ -z "${DEFAULT_ADMIN_HOME_IP:-}" ]; then
|
if [ -z "${DEFAULT_ADMIN_HOME_IP:-}" ]; then
|
||||||
input_box "Admin Home IP Address" \
|
input_box "Admin Home IP Address" \
|
||||||
"Enter the public IP address of the admin home, as given to you by your ISP.
|
"Enter the public IP address of the admin home, as given to you by your ISP.
|
||||||
|
This will be used to prevent banning of the administrator IP address.
|
||||||
\n\nAdmin Home IP address:" \
|
\n\nAdmin Home IP address:" \
|
||||||
"" \
|
"" \
|
||||||
ADMIN_HOME_IP
|
ADMIN_HOME_IP
|
||||||
|
@ -112,7 +112,7 @@ apt_get_quiet autoremove
|
|||||||
# * openssh-client: provides ssh-keygen
|
# * openssh-client: provides ssh-keygen
|
||||||
|
|
||||||
echo Installing system packages...
|
echo Installing system packages...
|
||||||
apt_install python3 python3-dev python3-pip \
|
apt_install python3 python3-dev python3-pip python3-setuptools \
|
||||||
netcat-openbsd wget curl git sudo coreutils bc \
|
netcat-openbsd wget curl git sudo coreutils bc \
|
||||||
haveged pollinate openssh-client unzip \
|
haveged pollinate openssh-client unzip \
|
||||||
unattended-upgrades cron ntp fail2ban rsyslog
|
unattended-upgrades cron ntp fail2ban rsyslog
|
||||||
|
@ -28,10 +28,11 @@ apt_install \
|
|||||||
# Install Roundcube from source if it is not already present or if it is out of date.
|
# Install Roundcube from source if it is not already present or if it is out of date.
|
||||||
# Combine the Roundcube version number with the commit hash of plugins to track
|
# Combine the Roundcube version number with the commit hash of plugins to track
|
||||||
# whether we have the latest version of everything.
|
# whether we have the latest version of everything.
|
||||||
VERSION=1.4.10
|
|
||||||
HASH=36b2351030e1ebddb8e39190d7b0ba82b1bbec1b
|
VERSION=1.4.11
|
||||||
PERSISTENT_LOGIN_VERSION=6b3fc450cae23ccb2f393d0ef67aa319e877e435
|
HASH=3877f0e70f29e7d0612155632e48c3db1e626be3
|
||||||
HTML5_NOTIFIER_VERSION=4b370e3cd60dabd2f428a26f45b677ad1b7118d5
|
PERSISTENT_LOGIN_VERSION=6b3fc450cae23ccb2f393d0ef67aa319e877e435 # version 5.2.0
|
||||||
|
HTML5_NOTIFIER_VERSION=68d9ca194212e15b3c7225eb6085dbcf02fd13d7 # version 0.6.4+
|
||||||
CARDDAV_VERSION=4.1.1
|
CARDDAV_VERSION=4.1.1
|
||||||
CARDDAV_HASH=87b73661b7799b2079c28324311eddb4241242bb
|
CARDDAV_HASH=87b73661b7799b2079c28324311eddb4241242bb
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@ apt_install \
|
|||||||
phpenmod -v php imap
|
phpenmod -v php imap
|
||||||
|
|
||||||
# Copy Z-Push into place.
|
# Copy Z-Push into place.
|
||||||
VERSION=2.6.1
|
VERSION=2.6.2
|
||||||
TARGETHASH=a4415f0dc0ed884acc8ad5c506944fc7e6d68eeb
|
TARGETHASH=4b312d64227ef887b24d9cc8f0ae17519586f6e2
|
||||||
needs_update=0 #NODOC
|
needs_update=0 #NODOC
|
||||||
if [ ! -f /usr/local/lib/z-push/version ]; then
|
if [ ! -f /usr/local/lib/z-push/version ]; then
|
||||||
needs_update=1 #NODOC
|
needs_update=1 #NODOC
|
||||||
@ -102,7 +102,7 @@ EOF
|
|||||||
|
|
||||||
# Restart service.
|
# Restart service.
|
||||||
|
|
||||||
restart_service php$(php_version)-fpm
|
restart_service php7.2-fpm
|
||||||
|
|
||||||
# Fix states after upgrade
|
# Fix states after upgrade
|
||||||
|
|
||||||
|
@ -22,14 +22,53 @@
|
|||||||
# NAME VAL
|
# NAME VAL
|
||||||
# UE
|
# UE
|
||||||
|
|
||||||
# create the new config file in memory
|
|
||||||
|
|
||||||
import sys, re
|
import sys, re
|
||||||
|
|
||||||
def edit_conf(filename, settings, delimiter_re, delimiter, comment_char, folded_lines = False, testing = False):
|
# 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()
|
found = set()
|
||||||
buf = ""
|
buf = ""
|
||||||
input_lines = list(open(filename, "r+"))
|
input_lines = list(open(filename))
|
||||||
|
|
||||||
while len(input_lines) > 0:
|
while len(input_lines) > 0:
|
||||||
line = input_lines.pop(0)
|
line = input_lines.pop(0)
|
||||||
@ -96,48 +135,3 @@ def edit_conf(filename, settings, delimiter_re, delimiter, comment_char, folded_
|
|||||||
else:
|
else:
|
||||||
# Just print the new file to stdout.
|
# Just print the new file to stdout.
|
||||||
print(buf)
|
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)
|
|
||||||
|
Loading…
Reference in New Issue
Block a user