refactor: Remove mailinabox packages, replaced by secubox-app-mailserver

Deleted:
- secubox-app-mailinabox (Docker-based)
- luci-app-mailinabox

Replaced by custom native implementation:
- secubox-app-mailserver (LXC Postfix+Dovecot)
- luci-app-mailserver

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-02-05 10:52:03 +01:00
parent 04d61baa6f
commit 58fe0909ac
12 changed files with 0 additions and 1341 deletions

View File

@ -1,32 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-only
# Copyright (C) 2025 CyberMind.fr
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for Mail-in-a-Box
LUCI_DEPENDS:=+luci-base
LUCI_PKGARCH:=all
PKG_NAME:=luci-app-mailinabox
PKG_VERSION:=1.0.0
PKG_RELEASE:=1
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
PKG_LICENSE:=GPL-2.0
include $(TOPDIR)/feeds/luci/luci.mk
define Package/luci-app-mailinabox/install
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.mailinabox $(1)/usr/libexec/rpcd/luci.mailinabox
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-mailinabox.json $(1)/usr/share/luci/menu.d/luci-app-mailinabox.json
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-mailinabox.json $(1)/usr/share/rpcd/acl.d/luci-app-mailinabox.json
$(INSTALL_DIR) $(1)/www/luci-static/resources/view/mailinabox
$(INSTALL_DATA) ./htdocs/luci-static/resources/view/mailinabox/*.js $(1)/www/luci-static/resources/view/mailinabox/
endef
$(eval $(call BuildPackage,$(PKG_NAME)))

View File

@ -1,41 +0,0 @@
# LuCI Mail-in-a-Box
Self-hosted mail server management dashboard (SMTP, IMAP, webmail).
## Installation
```bash
opkg install luci-app-mailinabox
```
## Access
LuCI menu: **Services -> Mail Server**
## Tabs
- **Overview** -- Service status, container health, domain configuration
- **Settings** -- Hostname, domain, service control
## RPCD Methods
Backend: `luci.mailinabox`
| Method | Description |
|--------|-------------|
| `status` | Service and container status |
| `get_config` | Get mail server configuration |
| `save_config` | Save hostname and domain settings |
| `install` | Install Mail-in-a-Box container |
| `start` | Start mail services |
| `stop` | Stop mail services |
| `restart` | Restart mail services |
| `logs` | Fetch service logs |
## Dependencies
- `luci-base`
## License
Apache-2.0

View File

@ -1,103 +0,0 @@
'use strict';
'require view';
'require ui';
'require rpc';
'require poll';
var callStatus = rpc.declare({ object: 'luci.mailinabox', method: 'status', expect: {} });
var callInstall = rpc.declare({ object: 'luci.mailinabox', method: 'install', expect: {} });
var callStart = rpc.declare({ object: 'luci.mailinabox', method: 'start', expect: {} });
var callStop = rpc.declare({ object: 'luci.mailinabox', method: 'stop', expect: {} });
var callRestart = rpc.declare({ object: 'luci.mailinabox', method: 'restart', expect: {} });
var css = '.mb-container{max-width:900px;margin:0 auto}.mb-header{display:flex;justify-content:space-between;align-items:center;padding:1.5rem;background:linear-gradient(135deg,#3b82f6 0%,#1d4ed8 100%);border-radius:16px;color:#fff;margin-bottom:1.5rem}.mb-header h2{margin:0;font-size:1.5rem;display:flex;align-items:center;gap:.5rem}.mb-status{display:flex;align-items:center;gap:.5rem;padding:.5rem 1rem;border-radius:20px;font-size:.9rem}.mb-status.running{background:rgba(16,185,129,.2)}.mb-status.stopped{background:rgba(239,68,68,.2)}.mb-dot{width:10px;height:10px;border-radius:50%;animation:pulse 2s infinite}.mb-status.running .mb-dot{background:#10b981}.mb-status.stopped .mb-dot{background:#ef4444}@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}.mb-card{background:#fff;border-radius:12px;padding:1.5rem;box-shadow:0 2px 8px rgba(0,0,0,.08);margin-bottom:1rem}.mb-card-title{font-size:1.1rem;font-weight:600;margin-bottom:1rem;display:flex;align-items:center;gap:.5rem}.mb-info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:1rem}.mb-info-item{padding:1rem;background:#f8f9fa;border-radius:8px}.mb-info-label{font-size:.8rem;color:#666;margin-bottom:.25rem}.mb-info-value{font-size:1rem;font-weight:500}.mb-actions{display:flex;gap:.75rem;flex-wrap:wrap}.mb-btn{padding:.6rem 1.2rem;border-radius:8px;border:none;cursor:pointer;font-weight:500;transition:all .2s}.mb-btn-primary{background:linear-gradient(135deg,#3b82f6,#1d4ed8);color:#fff}.mb-btn-success{background:#10b981;color:#fff}.mb-btn-danger{background:#ef4444;color:#fff}.mb-btn-secondary{background:#6b7280;color:#fff}.mb-btn:disabled{opacity:.5;cursor:not-allowed}.mb-ports{display:flex;gap:1rem;flex-wrap:wrap;margin-top:1rem}.mb-port{padding:.5rem 1rem;background:#e0e7ff;border-radius:8px;font-size:.85rem}.mb-port-name{font-weight:600;color:#3b82f6}.mb-not-installed{text-align:center;padding:3rem}.mb-features{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:1rem;margin:1.5rem 0;text-align:left}.mb-feature{padding:.75rem;background:#eff6ff;border-radius:8px;font-size:.9rem}';
return view.extend({
load: function() { return callStatus(); },
handleInstall: function() {
ui.showModal(_('Installing Mail Server'), [E('p', { 'class': 'spinning' }, _('Installing...'))]);
callInstall().then(function(r) {
ui.hideModal();
if (r.success) ui.addNotification(null, E('p', r.message || _('Started')));
else ui.addNotification(null, E('p', _('Failed: ') + r.error), 'error');
}).catch(function(e) { ui.hideModal(); ui.addNotification(null, E('p', e.message), 'error'); });
},
handleStart: function() {
ui.showModal(_('Starting...'), [E('p', { 'class': 'spinning' }, _('Starting...'))]);
callStart().then(function(r) { ui.hideModal(); ui.addNotification(null, E('p', _('Started'))); })
.catch(function(e) { ui.hideModal(); ui.addNotification(null, E('p', e.message), 'error'); });
},
handleStop: function() {
ui.showModal(_('Stopping...'), [E('p', { 'class': 'spinning' }, _('Stopping...'))]);
callStop().then(function(r) { ui.hideModal(); ui.addNotification(null, E('p', _('Stopped'))); })
.catch(function(e) { ui.hideModal(); ui.addNotification(null, E('p', e.message), 'error'); });
},
render: function(status) {
if (!document.getElementById('mb-mail-styles')) {
var s = document.createElement('style'); s.id = 'mb-mail-styles'; s.textContent = css; document.head.appendChild(s);
}
if (!status.installed || !status.docker_available) {
return E('div', { 'class': 'mb-container' }, [
E('div', { 'class': 'mb-header' }, [
E('h2', {}, ['\ud83d\udce7 ', _('Mail Server')]),
E('div', { 'class': 'mb-status stopped' }, [E('span', { 'class': 'mb-dot' }), E('span', {}, _('Not Installed'))])
]),
E('div', { 'class': 'mb-card' }, [
E('div', { 'class': 'mb-not-installed' }, [
E('div', { 'style': 'font-size:4rem;margin-bottom:1rem' }, '\ud83d\udce7'),
E('h3', {}, _('Mail-in-a-Box')),
E('p', {}, _('Self-hosted email server with SMTP, IMAP, spam filtering, and webmail.')),
E('div', { 'class': 'mb-features' }, [
E('div', { 'class': 'mb-feature' }, '\ud83d\udce4 SMTP'),
E('div', { 'class': 'mb-feature' }, '\ud83d\udce5 IMAP'),
E('div', { 'class': 'mb-feature' }, '\ud83d\udee1 SpamAssassin'),
E('div', { 'class': 'mb-feature' }, '\ud83d\udd12 SSL/TLS'),
E('div', { 'class': 'mb-feature' }, '\ud83c\udf10 Webmail'),
E('div', { 'class': 'mb-feature' }, '\ud83d\udc80 Fail2ban')
]),
!status.docker_available ? E('div', { 'style': 'color:#ef4444;margin-bottom:1rem' }, _('Docker required')) : '',
E('button', { 'class': 'mb-btn mb-btn-primary', 'click': ui.createHandlerFn(this, 'handleInstall'), 'disabled': !status.docker_available }, _('Install Mail Server'))
])
])
]);
}
return E('div', { 'class': 'mb-container' }, [
E('div', { 'class': 'mb-header' }, [
E('h2', {}, ['\ud83d\udce7 ', _('Mail Server')]),
E('div', { 'class': 'mb-status ' + (status.running ? 'running' : 'stopped') }, [
E('span', { 'class': 'mb-dot' }),
E('span', {}, status.running ? _('Running') : _('Stopped'))
])
]),
E('div', { 'class': 'mb-card' }, [
E('div', { 'class': 'mb-card-title' }, ['\u2139\ufe0f ', _('Configuration')]),
E('div', { 'class': 'mb-info-grid' }, [
E('div', { 'class': 'mb-info-item' }, [E('div', { 'class': 'mb-info-label' }, _('Hostname')), E('div', { 'class': 'mb-info-value' }, status.hostname)]),
E('div', { 'class': 'mb-info-item' }, [E('div', { 'class': 'mb-info-label' }, _('Domain')), E('div', { 'class': 'mb-info-value' }, status.domain)]),
E('div', { 'class': 'mb-info-item' }, [E('div', { 'class': 'mb-info-label' }, _('Data Path')), E('div', { 'class': 'mb-info-value' }, status.data_path)])
]),
E('div', { 'class': 'mb-ports' }, [
E('div', { 'class': 'mb-port' }, [E('span', { 'class': 'mb-port-name' }, 'SMTP'), ' :' + status.smtp_port]),
E('div', { 'class': 'mb-port' }, [E('span', { 'class': 'mb-port-name' }, 'IMAP'), ' :' + status.imap_port]),
E('div', { 'class': 'mb-port' }, [E('span', { 'class': 'mb-port-name' }, 'IMAPS'), ' :' + status.imaps_port])
])
]),
E('div', { 'class': 'mb-card' }, [
E('div', { 'class': 'mb-card-title' }, ['\u26a1 ', _('Actions')]),
E('div', { 'class': 'mb-actions' }, [
E('button', { 'class': 'mb-btn mb-btn-success', 'click': ui.createHandlerFn(this, 'handleStart'), 'disabled': status.running }, _('Start')),
E('button', { 'class': 'mb-btn mb-btn-danger', 'click': ui.createHandlerFn(this, 'handleStop'), 'disabled': !status.running }, _('Stop')),
E('a', { 'href': L.url('admin', 'secubox', 'services', 'mailinabox', 'settings'), 'class': 'mb-btn mb-btn-secondary' }, _('Settings'))
])
])
]);
},
handleSaveApply: null, handleSave: null, handleReset: null
});

View File

@ -1,75 +0,0 @@
'use strict';
'require view';
'require form';
'require uci';
return view.extend({
load: function() { return uci.load('mailinabox'); },
render: function() {
var m, s, o;
m = new form.Map('mailinabox', _('Mail Server Settings'),
_('Configure your mail server. IMPORTANT: Set hostname and domain before installing.'));
s = m.section(form.TypedSection, 'mailinabox', _('General Settings'));
s.anonymous = true;
s.addremove = false;
o = s.option(form.Flag, 'enabled', _('Enabled'));
o.default = '0';
o = s.option(form.Value, 'hostname', _('Mail Hostname'),
_('Full hostname for mail server (e.g., mail.example.com)'));
o.default = 'mail.example.com';
o = s.option(form.Value, 'domain', _('Domain'),
_('Primary email domain (e.g., example.com)'));
o.default = 'example.com';
o = s.option(form.Value, 'data_path', _('Data Path'));
o.default = '/srv/mailserver';
o = s.option(form.Value, 'timezone', _('Timezone'));
o.default = 'UTC';
s = m.section(form.TypedSection, 'mailinabox', _('Ports'));
s.anonymous = true;
o = s.option(form.Value, 'smtp_port', _('SMTP Port'));
o.datatype = 'port';
o.default = '25';
o = s.option(form.Value, 'submission_port', _('Submission Port'));
o.datatype = 'port';
o.default = '587';
o = s.option(form.Value, 'imap_port', _('IMAP Port'));
o.datatype = 'port';
o.default = '143';
o = s.option(form.Value, 'imaps_port', _('IMAPS Port'));
o.datatype = 'port';
o.default = '993';
s = m.section(form.TypedSection, 'mailinabox', _('Features'));
s.anonymous = true;
o = s.option(form.Flag, 'enable_spamassassin', _('SpamAssassin'));
o.default = '1';
o = s.option(form.Flag, 'enable_clamav', _('ClamAV Antivirus'));
o.default = '0';
o = s.option(form.Flag, 'enable_fail2ban', _('Fail2ban'));
o.default = '1';
o = s.option(form.ListValue, 'ssl_type', _('SSL Type'));
o.value('letsencrypt', _("Let's Encrypt"));
o.value('manual', _('Manual'));
o.value('self-signed', _('Self-signed'));
o.default = 'letsencrypt';
return m.render();
}
});

View File

@ -1,139 +0,0 @@
#!/bin/sh
# RPCD backend for Mail-in-a-Box LuCI app
CONFIG="mailinabox"
CONTAINER="secbx-mailserver"
uci_get() { uci -q get ${CONFIG}.main.$1; }
uci_set() { uci set ${CONFIG}.main.$1="$2" && uci commit ${CONFIG}; }
get_status() {
local enabled=$(uci_get enabled)
local hostname=$(uci_get hostname)
local domain=$(uci_get domain)
local data_path=$(uci_get data_path)
local docker_available=0
command -v docker >/dev/null 2>&1 && docker_available=1
local running=0
local container_status="stopped"
if [ "$docker_available" = "1" ]; then
if docker ps --filter "name=$CONTAINER" --format "{{.Names}}" 2>/dev/null | grep -q "$CONTAINER"; then
running=1
container_status="running"
fi
fi
local installed=0
if [ "$docker_available" = "1" ]; then
docker images --format "{{.Repository}}" 2>/dev/null | grep -q "docker-mailserver" && installed=1
fi
cat <<EOF
{
"enabled": $([ "$enabled" = "1" ] && echo "true" || echo "false"),
"running": $([ "$running" = "1" ] && echo "true" || echo "false"),
"installed": $([ "$installed" = "1" ] && echo "true" || echo "false"),
"docker_available": $([ "$docker_available" = "1" ] && echo "true" || echo "false"),
"container_status": "$container_status",
"hostname": "${hostname:-mail.example.com}",
"domain": "${domain:-example.com}",
"data_path": "${data_path:-/srv/mailserver}",
"smtp_port": $(uci_get smtp_port || echo 25),
"imap_port": $(uci_get imap_port || echo 143),
"imaps_port": $(uci_get imaps_port || echo 993)
}
EOF
}
get_config() {
cat <<EOF
{
"enabled": "$(uci_get enabled || echo 0)",
"hostname": "$(uci_get hostname || echo mail.example.com)",
"domain": "$(uci_get domain || echo example.com)",
"data_path": "$(uci_get data_path || echo /srv/mailserver)",
"timezone": "$(uci_get timezone || echo UTC)",
"smtp_port": "$(uci_get smtp_port || echo 25)",
"submission_port": "$(uci_get submission_port || echo 587)",
"imap_port": "$(uci_get imap_port || echo 143)",
"imaps_port": "$(uci_get imaps_port || echo 993)",
"enable_spamassassin": "$(uci_get enable_spamassassin || echo 1)",
"enable_clamav": "$(uci_get enable_clamav || echo 0)",
"ssl_type": "$(uci_get ssl_type || echo letsencrypt)"
}
EOF
}
save_config() {
local input; read -r input
local hostname=$(echo "$input" | jsonfilter -e '@.hostname' 2>/dev/null)
local domain=$(echo "$input" | jsonfilter -e '@.domain' 2>/dev/null)
[ -n "$hostname" ] && uci_set hostname "$hostname"
[ -n "$domain" ] && uci_set domain "$domain"
echo '{"success": true}'
}
do_install() {
if command -v mailinaboxctl >/dev/null 2>&1; then
mailinaboxctl install >/tmp/mailinabox-install.log 2>&1 &
echo '{"success": true, "message": "Installation started"}'
else
echo '{"success": false, "error": "mailinaboxctl not found"}'
fi
}
do_start() {
[ -x /etc/init.d/mailinabox ] && /etc/init.d/mailinabox start >/dev/null 2>&1 && uci_set enabled '1'
echo '{"success": true}'
}
do_stop() {
[ -x /etc/init.d/mailinabox ] && /etc/init.d/mailinabox stop >/dev/null 2>&1
echo '{"success": true}'
}
do_restart() {
[ -x /etc/init.d/mailinabox ] && /etc/init.d/mailinabox restart >/dev/null 2>&1
echo '{"success": true}'
}
get_logs() {
local log_content=""
[ -f /tmp/mailinabox-install.log ] && log_content=$(tail -n 50 /tmp/mailinabox-install.log 2>/dev/null | sed 's/"/\\"/g' | tr '\n' '|')
echo "{\"logs\": \"$log_content\"}"
}
list_methods() {
cat <<'EOF'
{
"status": {},
"get_config": {},
"save_config": {"hostname": "string", "domain": "string"},
"install": {},
"start": {},
"stop": {},
"restart": {},
"logs": {}
}
EOF
}
case "$1" in
list) list_methods ;;
call)
case "$2" in
status) get_status ;;
get_config) get_config ;;
save_config) save_config ;;
install) do_install ;;
start) do_start ;;
stop) do_stop ;;
restart) do_restart ;;
logs) get_logs ;;
*) echo '{"error": "Unknown method"}' ;;
esac
;;
*) echo '{"error": "Unknown command"}' ;;
esac

View File

@ -1,28 +0,0 @@
{
"admin/services/mailinabox": {
"title": "Mail Server",
"order": 60,
"action": {
"type": "firstchild"
},
"depends": {
"acl": ["luci-app-mailinabox"]
}
},
"admin/services/mailinabox/overview": {
"title": "Overview",
"order": 10,
"action": {
"type": "view",
"path": "mailinabox/overview"
}
},
"admin/services/mailinabox/settings": {
"title": "Settings",
"order": 90,
"action": {
"type": "view",
"path": "mailinabox/settings"
}
}
}

View File

@ -1,17 +0,0 @@
{
"luci-app-mailinabox": {
"description": "Grant access to Mail-in-a-Box",
"read": {
"ubus": {
"luci.mailinabox": ["status", "get_config", "logs"]
},
"uci": ["mailinabox"]
},
"write": {
"ubus": {
"luci.mailinabox": ["install", "start", "stop", "restart", "update", "save_config", "add_account", "list_accounts"]
},
"uci": ["mailinabox"]
}
}
}

View File

@ -1,57 +0,0 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=secubox-app-mailinabox
PKG_RELEASE:=1
PKG_VERSION:=2.0.0
PKG_ARCH:=all
PKG_MAINTAINER:=CyberMind Studio <contact@cybermind.fr>
PKG_LICENSE:=CC0-1.0
include $(INCLUDE_DIR)/package.mk
define Package/secubox-app-mailinabox
SECTION:=utils
CATEGORY:=Utilities
PKGARCH:=all
SUBMENU:=SecuBox Apps
TITLE:=SecuBox Mail Server (docker-mailserver)
DEPENDS:=
endef
define Package/secubox-app-mailinabox/description
Complete email server solution using docker-mailserver for SecuBox.
Features:
- Full email server (SMTP, IMAP, POP3)
- User account management (add/remove/list)
- Email aliases support
- SpamAssassin spam filtering
- ClamAV antivirus (optional)
- Fail2ban intrusion prevention
- Let's Encrypt SSL certificates
- Backup and restore functionality
- DNS configuration verification
- Health monitoring and diagnostics
Commands: mailinaboxctl --help
endef
define Package/secubox-app-mailinabox/conffiles
/etc/config/mailinabox
endef
define Build/Compile
endef
define Package/secubox-app-mailinabox/install
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./files/etc/config/mailinabox $(1)/etc/config/mailinabox
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/etc/init.d/mailinabox $(1)/etc/init.d/mailinabox
$(INSTALL_DIR) $(1)/usr/sbin
$(INSTALL_BIN) ./files/usr/sbin/mailinaboxctl $(1)/usr/sbin/mailinaboxctl
endef
$(eval $(call BuildPackage,secubox-app-mailinabox))

View File

@ -1,52 +0,0 @@
# SecuBox Mail Server (docker-mailserver)
Full-featured mail server with SMTP, IMAP, POP3, spam filtering, antivirus, and automatic Let's Encrypt certificates. Runs docker-mailserver in a managed Docker container on OpenWrt.
## Installation
```bash
opkg install secubox-app-mailinabox
```
## Configuration
UCI config file: `/etc/config/mailinabox`
```bash
uci set mailinabox.main.enabled='1'
uci set mailinabox.main.hostname='mail.example.com'
uci set mailinabox.main.domain='example.com'
uci set mailinabox.main.ssl='letsencrypt'
uci commit mailinabox
```
## Usage
```bash
mailinaboxctl start # Start mail server
mailinaboxctl stop # Stop mail server
mailinaboxctl status # Show service status
mailinaboxctl user add <email> # Add mail user
mailinaboxctl user list # List mail users
mailinaboxctl user del <email> # Remove mail user
mailinaboxctl logs # View mail logs
```
## Features
- SMTP (25/587), IMAP (993), POP3 (995)
- SpamAssassin spam filtering
- ClamAV antivirus scanning
- DKIM/SPF/DMARC support
- Automatic Let's Encrypt TLS certificates
- User and alias management via CLI
## Dependencies
- `dockerd`
- `docker`
- `containerd`
## License
Apache-2.0

View File

@ -1,30 +0,0 @@
config mailinabox 'main'
option enabled '0'
option image 'ghcr.io/docker-mailserver/docker-mailserver:latest'
option data_path '/srv/mailserver'
# Domain configuration (MUST be configured before use)
option hostname 'mail.example.com'
option domain 'example.com'
option timezone 'UTC'
# Port mappings
option smtp_port '25'
option submission_port '587'
option submissions_port '465'
option imap_port '143'
option imaps_port '993'
option pop3_port '110'
option pop3s_port '995'
# Feature flags
option enable_pop3 '0'
option enable_clamav '0'
option enable_spamassassin '1'
option enable_fail2ban '1'
# SSL configuration
# Options: letsencrypt, manual, self-signed
option ssl_type 'letsencrypt'
# Email for Let's Encrypt notifications (optional but recommended)
#option letsencrypt_email 'admin@example.com'

View File

@ -1,42 +0,0 @@
#!/bin/sh /etc/rc.common
START=95
STOP=10
USE_PROCD=1
EXTRA_COMMANDS="status"
EXTRA_HELP=" status Show mail server status"
SERVICE_BIN="/usr/sbin/mailinaboxctl"
start_service() {
local enabled
config_load mailinabox
config_get enabled main enabled 0
[ "$enabled" != "1" ] && {
echo "Mail server is disabled. Enable with: uci set mailinabox.main.enabled=1"
return 0
}
procd_open_instance
procd_set_param command "$SERVICE_BIN" service-run
procd_set_param respawn 3600 5 5
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance
}
stop_service() {
"$SERVICE_BIN" service-stop >/dev/null 2>&1
}
restart_service() {
stop_service
sleep 2
start_service
}
status() {
"$SERVICE_BIN" status
}

View File

@ -1,725 +0,0 @@
#!/bin/sh
# SecuBox Mail-in-a-Box manager - Docker Mailserver Edition
# Copyright (C) 2024 CyberMind.fr
#
# Based on docker-mailserver for lightweight email hosting
CONFIG="mailinabox"
CONTAINER_NAME="secbx-mailserver"
OPKG_UPDATED=0
# Paths
DATA_BASE="/srv/mailserver"
usage() {
cat <<'EOF'
Usage: mailinaboxctl <command>
Container Management:
install Install prerequisites, prepare directories, pull image
check Run prerequisite checks (ports, DNS, storage)
update Pull new image and restart
status Show container and service status
logs Show container logs (use -f to follow)
shell Open shell in container
service-run Internal: run container via procd
service-stop Stop container
Email Account Management:
user-add <email> [password] Add email account
user-del <email> Remove email account
user-list List all email accounts
user-passwd <email> Change user password
alias-add <alias> <target> Add email alias
alias-del <alias> Remove email alias
alias-list List all aliases
Domain & SSL:
domain-add <domain> Add email domain
domain-list List configured domains
ssl-status Show SSL certificate status
ssl-renew Force SSL certificate renewal
Backup & Restore:
backup [path] Backup mail data and config
restore <backup-file> Restore from backup
Diagnostics:
health Run health checks
dns-check [domain] Verify DNS records for domain
ports Check required ports
config Show current configuration
test-email <to> Send test email
Post-Installation:
1. Configure hostname and domain in /etc/config/mailinabox
2. Set proper DNS records (A, MX, SPF, DKIM, DMARC)
3. Start with: /etc/init.d/mailinabox start
4. Add users with: mailinaboxctl user-add admin@yourdomain.com
Required DNS Records:
A mail.domain.com -> your-public-ip
MX domain.com -> mail.domain.com (priority 10)
TXT domain.com -> "v=spf1 mx -all"
TXT _dmarc.domain.com -> "v=DMARC1; p=quarantine"
TXT mail._domainkey.domain.com -> (DKIM key from container)
EOF
}
require_root() { [ "$(id -u)" -eq 0 ] || { echo "Root required" >&2; exit 1; }; }
log_info() { echo "[INFO] $*"; }
log_warn() { echo "[WARN] $*" >&2; }
log_error() { echo "[ERROR] $*" >&2; }
uci_get() { uci -q get ${CONFIG}.main.$1; }
uci_set() { uci set ${CONFIG}.main.$1="$2" && uci commit ${CONFIG}; }
# Load configuration with defaults
load_config() {
enabled="$(uci_get enabled || echo 0)"
image="$(uci_get image || echo ghcr.io/docker-mailserver/docker-mailserver:latest)"
data_path="$(uci_get data_path || echo /srv/mailserver)"
hostname="$(uci_get hostname || echo mail.example.com)"
domain="$(uci_get domain || echo example.com)"
timezone="$(uci_get timezone || cat /etc/TZ 2>/dev/null || echo UTC)"
# Ports
smtp_port="$(uci_get smtp_port || echo 25)"
submission_port="$(uci_get submission_port || echo 587)"
submissions_port="$(uci_get submissions_port || echo 465)"
imap_port="$(uci_get imap_port || echo 143)"
imaps_port="$(uci_get imaps_port || echo 993)"
pop3_port="$(uci_get pop3_port || echo 110)"
pop3s_port="$(uci_get pop3s_port || echo 995)"
# Features
enable_pop3="$(uci_get enable_pop3 || echo 0)"
enable_clamav="$(uci_get enable_clamav || echo 0)"
enable_spamassassin="$(uci_get enable_spamassassin || echo 1)"
enable_fail2ban="$(uci_get enable_fail2ban || echo 1)"
ssl_type="$(uci_get ssl_type || echo letsencrypt)"
letsencrypt_email="$(uci_get letsencrypt_email)"
}
ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; }
# =============================================================================
# Docker Functions
# =============================================================================
ensure_packages() {
for pkg in "$@"; do
if ! opkg list-installed 2>/dev/null | grep -q "^$pkg "; then
if [ "$OPKG_UPDATED" -eq 0 ]; then
opkg update || return 1
OPKG_UPDATED=1
fi
opkg install "$pkg" || return 1
fi
done
}
docker_ready() {
command -v docker >/dev/null 2>&1 && [ -S /var/run/docker.sock ]
}
check_prereqs() {
load_config
log_info "Checking prerequisites..."
# Check hostname configuration
if [ "$hostname" = "mail.example.com" ] || [ "$domain" = "example.com" ]; then
log_warn "Please configure hostname and domain in /etc/config/mailinabox"
log_warn "docker-mailserver requires a valid domain name"
fi
# Check cgroups
[ -d /sys/fs/cgroup ] || { log_error "/sys/fs/cgroup missing"; return 1; }
# Install Docker
ensure_packages dockerd docker containerd || return 1
# Enable and start Docker
/etc/init.d/dockerd enable >/dev/null 2>&1
if ! /etc/init.d/dockerd status >/dev/null 2>&1; then
/etc/init.d/dockerd start || return 1
sleep 3
fi
# Wait for Docker socket
local retry=0
while [ ! -S /var/run/docker.sock ] && [ $retry -lt 30 ]; do
sleep 1
retry=$((retry + 1))
done
[ -S /var/run/docker.sock ] || { log_error "Docker socket not available"; return 1; }
# Create data directories
ensure_dir "$data_path"
ensure_dir "$data_path/mail-data"
ensure_dir "$data_path/mail-state"
ensure_dir "$data_path/mail-logs"
ensure_dir "$data_path/config"
log_info "Docker ready, directories created"
return 0
}
pull_image() {
load_config
log_info "Pulling Docker image: $image"
docker pull "$image"
}
stop_container() {
docker stop "$CONTAINER_NAME" >/dev/null 2>&1 || true
docker rm "$CONTAINER_NAME" >/dev/null 2>&1 || true
}
container_running() {
docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER_NAME}$"
}
# Execute setup.sh in container
docker_setup() {
if ! container_running; then
log_error "Container not running. Start with: /etc/init.d/mailinabox start"
return 1
fi
docker exec -it "$CONTAINER_NAME" setup "$@"
}
# =============================================================================
# User Management Commands
# =============================================================================
cmd_user_add() {
local email="$1"
local password="$2"
[ -z "$email" ] && { log_error "Usage: mailinaboxctl user-add <email> [password]"; return 1; }
if [ -n "$password" ]; then
docker_setup email add "$email" "$password"
else
docker_setup email add "$email"
fi
}
cmd_user_del() {
local email="$1"
[ -z "$email" ] && { log_error "Usage: mailinaboxctl user-del <email>"; return 1; }
docker_setup email del "$email"
}
cmd_user_list() {
docker_setup email list
}
cmd_user_passwd() {
local email="$1"
[ -z "$email" ] && { log_error "Usage: mailinaboxctl user-passwd <email>"; return 1; }
docker_setup email update "$email"
}
cmd_alias_add() {
local alias="$1"
local target="$2"
[ -z "$alias" ] || [ -z "$target" ] && { log_error "Usage: mailinaboxctl alias-add <alias> <target>"; return 1; }
docker_setup alias add "$alias" "$target"
}
cmd_alias_del() {
local alias="$1"
[ -z "$alias" ] && { log_error "Usage: mailinaboxctl alias-del <alias>"; return 1; }
docker_setup alias del "$alias"
}
cmd_alias_list() {
docker_setup alias list
}
# =============================================================================
# Domain & SSL Commands
# =============================================================================
cmd_domain_add() {
local domain="$1"
[ -z "$domain" ] && { log_error "Usage: mailinaboxctl domain-add <domain>"; return 1; }
# Create virtual domain entry
load_config
local vhost_file="$data_path/config/postfix-virtual.cf"
if ! grep -q "^$domain" "$vhost_file" 2>/dev/null; then
echo "$domain" >> "$vhost_file"
log_info "Domain $domain added. Restart service to apply."
else
log_warn "Domain $domain already exists"
fi
}
cmd_domain_list() {
load_config
log_info "Configured domains:"
if [ -f "$data_path/config/postfix-virtual.cf" ]; then
cat "$data_path/config/postfix-virtual.cf" | grep -v "^#" | grep -v "^$"
fi
echo ""
echo "Primary domain: $domain"
echo "Mail hostname: $hostname"
}
cmd_ssl_status() {
load_config
log_info "SSL Configuration:"
echo " Type: $ssl_type"
if container_running; then
docker_setup debug show-mail-logs | grep -i "ssl\|cert\|tls" | tail -20
fi
# Check certificate files
if [ -d "$data_path/config/ssl" ]; then
echo ""
echo "Certificate files:"
ls -la "$data_path/config/ssl/" 2>/dev/null || echo " No certificates found"
fi
}
cmd_ssl_renew() {
load_config
if [ "$ssl_type" = "letsencrypt" ]; then
log_info "Triggering Let's Encrypt renewal..."
if container_running; then
docker exec "$CONTAINER_NAME" certbot renew
else
log_error "Container not running"
fi
else
log_warn "SSL type is not letsencrypt"
fi
}
# =============================================================================
# Backup & Restore Commands
# =============================================================================
cmd_backup() {
require_root
load_config
local backup_path="${1:-/tmp}"
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="$backup_path/mailserver_backup_$timestamp.tar.gz"
log_info "Creating backup..."
# Stop container for consistent backup
local was_running=0
if container_running; then
was_running=1
log_info "Stopping container for backup..."
stop_container
fi
# Create backup
tar czf "$backup_file" -C "$(dirname $data_path)" "$(basename $data_path)" 2>/dev/null || {
log_error "Backup failed"
[ $was_running -eq 1 ] && /etc/init.d/mailinabox start
return 1
}
# Restart if was running
[ $was_running -eq 1 ] && /etc/init.d/mailinabox start
log_info "Backup created: $backup_file"
ls -lh "$backup_file"
}
cmd_restore() {
require_root
load_config
local backup_file="$1"
[ -z "$backup_file" ] || [ ! -f "$backup_file" ] && {
log_error "Usage: mailinaboxctl restore <backup-file>"
return 1
}
log_warn "This will OVERWRITE existing mail data!"
echo -n "Continue? [y/N] "
read answer
[ "$answer" != "y" ] && [ "$answer" != "Y" ] && { echo "Aborted"; return 1; }
# Stop container
if container_running; then
log_info "Stopping container..."
stop_container
fi
# Remove existing data
log_info "Removing existing data..."
rm -rf "$data_path"
# Restore
log_info "Restoring from backup..."
tar xzf "$backup_file" -C "$(dirname $data_path)" || {
log_error "Restore failed"
return 1
}
log_info "Restore complete. Start service with: /etc/init.d/mailinabox start"
}
# =============================================================================
# Diagnostic Commands
# =============================================================================
cmd_health() {
load_config
echo "=== Mail Server Health Check ==="
echo ""
# Container status
echo "Container Status:"
if container_running; then
echo " [OK] Container is running"
local uptime=$(docker inspect --format='{{.State.StartedAt}}' "$CONTAINER_NAME" 2>/dev/null)
echo " Started: $uptime"
else
echo " [FAIL] Container is not running"
fi
echo ""
# Port checks
echo "Port Status:"
for port in $smtp_port $submission_port $imaps_port; do
if netstat -tln 2>/dev/null | grep -q ":$port "; then
echo " [OK] Port $port is listening"
else
echo " [WARN] Port $port is not listening"
fi
done
echo ""
# Service checks inside container
if container_running; then
echo "Services Status:"
docker exec "$CONTAINER_NAME" supervisorctl status 2>/dev/null || echo " Unable to check services"
fi
echo ""
# Disk usage
echo "Disk Usage:"
if [ -d "$data_path" ]; then
du -sh "$data_path" 2>/dev/null
du -sh "$data_path"/* 2>/dev/null | head -10
fi
}
cmd_dns_check() {
load_config
local check_domain="${1:-$domain}"
echo "=== DNS Check for $check_domain ==="
echo ""
# A record
echo "A Record (mail.$check_domain):"
local a_record=$(nslookup "mail.$check_domain" 2>/dev/null | grep -A1 "Name:" | tail -1)
if [ -n "$a_record" ]; then
echo " [OK] $a_record"
else
echo " [FAIL] No A record found"
fi
echo ""
# MX record
echo "MX Record ($check_domain):"
local mx_record=$(nslookup -type=mx "$check_domain" 2>/dev/null | grep "mail exchanger")
if [ -n "$mx_record" ]; then
echo " [OK] $mx_record"
else
echo " [FAIL] No MX record found"
fi
echo ""
# SPF record
echo "SPF Record ($check_domain):"
local spf=$(nslookup -type=txt "$check_domain" 2>/dev/null | grep "v=spf1")
if [ -n "$spf" ]; then
echo " [OK] $spf"
else
echo " [WARN] No SPF record found"
echo " Recommended: \"v=spf1 mx -all\""
fi
echo ""
# DMARC record
echo "DMARC Record (_dmarc.$check_domain):"
local dmarc=$(nslookup -type=txt "_dmarc.$check_domain" 2>/dev/null | grep "v=DMARC1")
if [ -n "$dmarc" ]; then
echo " [OK] $dmarc"
else
echo " [WARN] No DMARC record found"
echo " Recommended: \"v=DMARC1; p=quarantine; rua=mailto:postmaster@$check_domain\""
fi
}
cmd_ports() {
load_config
echo "=== Port Status ==="
echo ""
echo "Required ports for mail server:"
echo ""
local ports="25:SMTP 587:Submission 465:SMTPS 143:IMAP 993:IMAPS"
[ "$enable_pop3" = "1" ] && ports="$ports 110:POP3 995:POP3S"
for entry in $ports; do
local port=$(echo "$entry" | cut -d: -f1)
local name=$(echo "$entry" | cut -d: -f2)
printf " %-6s %-12s " "$port" "$name"
if netstat -tln 2>/dev/null | grep -q ":$port "; then
echo "[LISTENING]"
else
echo "[NOT LISTENING]"
fi
done
echo ""
echo "Note: Port 25 may be blocked by some ISPs"
}
cmd_config() {
load_config
echo "=== Mail Server Configuration ==="
echo ""
echo "General:"
echo " Enabled: $enabled"
echo " Image: $image"
echo " Hostname: $hostname"
echo " Domain: $domain"
echo " Data path: $data_path"
echo " Timezone: $timezone"
echo ""
echo "Features:"
echo " SpamAssassin: $enable_spamassassin"
echo " ClamAV: $enable_clamav"
echo " Fail2ban: $enable_fail2ban"
echo " POP3: $enable_pop3"
echo " SSL Type: $ssl_type"
echo ""
echo "Ports:"
echo " SMTP: $smtp_port"
echo " Submission: $submission_port"
echo " IMAPS: $imaps_port"
}
cmd_test_email() {
local to="$1"
[ -z "$to" ] && { log_error "Usage: mailinaboxctl test-email <to-address>"; return 1; }
load_config
if ! container_running; then
log_error "Container not running"
return 1
fi
log_info "Sending test email to $to..."
docker exec "$CONTAINER_NAME" sh -c "echo 'Test email from SecuBox Mail Server' | mail -s 'Test from $hostname' $to"
log_info "Test email sent (check spam folder if not received)"
}
# =============================================================================
# Main Container Commands
# =============================================================================
cmd_install() {
require_root
check_prereqs || exit 1
pull_image || exit 1
uci_set enabled '1'
uci commit ${CONFIG}
/etc/init.d/mailinabox enable
load_config
echo ""
log_info "Mail server installed successfully!"
echo ""
echo "NEXT STEPS:"
echo " 1. Edit /etc/config/mailinabox and configure:"
echo " - hostname (e.g., mail.yourdomain.com)"
echo " - domain (e.g., yourdomain.com)"
echo " 2. Set up DNS records (see 'mailinaboxctl dns-check')"
echo " 3. Start: /etc/init.d/mailinabox start"
echo " 4. Add first user: mailinaboxctl user-add admin@$domain"
echo ""
}
cmd_check() {
check_prereqs
echo ""
cmd_config
echo ""
cmd_ports
}
cmd_update() {
require_root
pull_image || exit 1
if container_running; then
/etc/init.d/mailinabox restart
else
log_info "Image updated. Start manually when ready."
fi
}
cmd_status() {
echo "=== Container Status ==="
docker ps -a --filter "name=$CONTAINER_NAME" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
echo ""
if container_running; then
echo "=== Service Status ==="
docker exec "$CONTAINER_NAME" supervisorctl status 2>/dev/null || true
fi
}
cmd_logs() {
docker logs "$@" "$CONTAINER_NAME"
}
cmd_shell() {
if container_running; then
docker exec -it "$CONTAINER_NAME" /bin/bash
else
log_error "Container not running"
fi
}
cmd_service_run() {
require_root
check_prereqs || exit 1
load_config
stop_container
log_info "Starting mail server container..."
# Build docker run command
local docker_args="--name $CONTAINER_NAME"
# Hostname
docker_args="$docker_args --hostname $hostname"
docker_args="$docker_args --domainname $domain"
# Ports
docker_args="$docker_args -p $smtp_port:25"
docker_args="$docker_args -p $submission_port:587"
docker_args="$docker_args -p $submissions_port:465"
docker_args="$docker_args -p $imap_port:143"
docker_args="$docker_args -p $imaps_port:993"
if [ "$enable_pop3" = "1" ]; then
docker_args="$docker_args -p $pop3_port:110"
docker_args="$docker_args -p $pop3s_port:995"
fi
# Volumes
docker_args="$docker_args -v $data_path/mail-data:/var/mail"
docker_args="$docker_args -v $data_path/mail-state:/var/mail-state"
docker_args="$docker_args -v $data_path/mail-logs:/var/log/mail"
docker_args="$docker_args -v $data_path/config:/tmp/docker-mailserver"
# Let's Encrypt volume if using certbot
if [ "$ssl_type" = "letsencrypt" ]; then
ensure_dir "$data_path/letsencrypt"
docker_args="$docker_args -v $data_path/letsencrypt:/etc/letsencrypt"
fi
# Environment variables
docker_args="$docker_args -e TZ=$timezone"
docker_args="$docker_args -e OVERRIDE_HOSTNAME=$hostname"
docker_args="$docker_args -e ENABLE_SPAMASSASSIN=$enable_spamassassin"
docker_args="$docker_args -e ENABLE_CLAMAV=$enable_clamav"
docker_args="$docker_args -e ENABLE_FAIL2BAN=$enable_fail2ban"
docker_args="$docker_args -e ENABLE_POP3=$enable_pop3"
docker_args="$docker_args -e SSL_TYPE=$ssl_type"
docker_args="$docker_args -e PERMIT_DOCKER=network"
docker_args="$docker_args -e ONE_DIR=1"
docker_args="$docker_args -e POSTMASTER_ADDRESS=postmaster@$domain"
if [ -n "$letsencrypt_email" ]; then
docker_args="$docker_args -e LETSENCRYPT_EMAIL=$letsencrypt_email"
fi
# Capabilities
docker_args="$docker_args --cap-add=NET_ADMIN"
docker_args="$docker_args --cap-add=SYS_PTRACE"
# Restart policy
docker_args="$docker_args --restart=unless-stopped"
exec docker run --rm $docker_args "$image"
}
cmd_service_stop() {
require_root
stop_container
}
# =============================================================================
# Main Entry Point
# =============================================================================
case "${1:-}" in
# Container management
install) shift; cmd_install "$@" ;;
check) shift; cmd_check "$@" ;;
update) shift; cmd_update "$@" ;;
status) shift; cmd_status "$@" ;;
logs) shift; cmd_logs "$@" ;;
shell) shift; cmd_shell "$@" ;;
service-run) shift; cmd_service_run "$@" ;;
service-stop) shift; cmd_service_stop "$@" ;;
# User management
user-add) shift; cmd_user_add "$@" ;;
user-del) shift; cmd_user_del "$@" ;;
user-list) shift; cmd_user_list "$@" ;;
user-passwd) shift; cmd_user_passwd "$@" ;;
alias-add) shift; cmd_alias_add "$@" ;;
alias-del) shift; cmd_alias_del "$@" ;;
alias-list) shift; cmd_alias_list "$@" ;;
# Domain & SSL
domain-add) shift; cmd_domain_add "$@" ;;
domain-list) shift; cmd_domain_list "$@" ;;
ssl-status) shift; cmd_ssl_status "$@" ;;
ssl-renew) shift; cmd_ssl_renew "$@" ;;
# Backup & restore
backup) shift; cmd_backup "$@" ;;
restore) shift; cmd_restore "$@" ;;
# Diagnostics
health) shift; cmd_health "$@" ;;
dns-check) shift; cmd_dns_check "$@" ;;
ports) shift; cmd_ports "$@" ;;
config) shift; cmd_config "$@" ;;
test-email) shift; cmd_test_email "$@" ;;
help|--help|-h|'') usage ;;
*) echo "Unknown command: $1" >&2; usage >&2; exit 1 ;;
esac