feat(luci-app-mailserver): Add unified mail server dashboard

All-in-one LuCI interface for:
- Mail server status and control
- User/alias management with modals
- Port status monitoring
- DNS/SSL setup actions
- Webmail configuration
- Mesh backup integration

RPCD handler with 17 methods for full mail management.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-02-05 10:43:55 +01:00
parent c6fb79ed3b
commit ded107e408
6 changed files with 951 additions and 0 deletions

View File

@ -0,0 +1,29 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-mailserver
PKG_VERSION:=1.0.0
PKG_RELEASE:=1
PKG_MAINTAINER:=SecuBox Team
PKG_LICENSE:=MIT
LUCI_TITLE:=LuCI Mail Server Manager
LUCI_DEPENDS:=+secubox-app-mailserver +luci-base
include $(TOPDIR)/feeds/luci/luci.mk
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/*.json $(1)/usr/share/luci/menu.d/
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/*.json $(1)/usr/share/rpcd/acl.d/
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.mailserver $(1)/usr/libexec/rpcd/
$(INSTALL_DIR) $(1)/www/luci-static/resources/view/mailserver
$(INSTALL_DATA) ./htdocs/luci-static/resources/view/mailserver/*.js $(1)/www/luci-static/resources/view/mailserver/
endef
$(eval $(call BuildPackage,$(PKG_NAME)))

View File

@ -0,0 +1,81 @@
# LuCI Mail Server Manager
Unified web dashboard for SecuBox mail server, webmail, and mesh backup.
## Features
- **Server Status**: Container state, domain, users, storage, SSL, mesh
- **Port Monitoring**: SMTP (25), Submission (587), SMTPS (465), IMAPS (993), POP3S (995)
- **User Management**: Add/delete mail accounts with mailbox stats
- **Alias Management**: Create email forwarding aliases
- **DNS Setup**: One-click MX, SPF, DMARC record creation
- **SSL Setup**: ACME DNS-01 certificate automation
- **Webmail Integration**: Configure Roundcube container
- **Mesh Backup**: P2P backup synchronization
## Installation
```bash
opkg install luci-app-mailserver
```
## Location
**Services → Mail Server**
## RPCD Methods
| Method | Parameters | Description |
|--------|------------|-------------|
| `status` | - | Get server status (state, domain, users, ports, SSL) |
| `user_list` | - | List mail users with mailbox stats |
| `alias_list` | - | List email aliases |
| `webmail_status` | - | Get webmail container status |
| `logs` | `lines` | Get mail server logs |
| `install` | - | Install mail server container |
| `start` | - | Start mail server |
| `stop` | - | Stop mail server |
| `restart` | - | Restart mail server |
| `user_add` | `email`, `password` | Add mail user |
| `user_del` | `email` | Delete mail user |
| `user_passwd` | `email`, `password` | Change user password |
| `alias_add` | `alias`, `target` | Add email alias |
| `dns_setup` | - | Create MX/SPF/DMARC records |
| `ssl_setup` | - | Obtain SSL certificate |
| `webmail_configure` | - | Configure Roundcube |
| `mesh_backup` | - | Create mesh backup |
| `mesh_sync` | `mode` | Sync with mesh (push/pull) |
## Dashboard Sections
### Server Status
- Container running state
- Domain FQDN
- User count
- Storage usage
- SSL certificate validity
- Webmail status
- Mesh backup status
- Port status indicators
### Quick Actions
- Start/Stop server
- Setup DNS records
- Setup SSL certificate
- Configure webmail
- Create mesh backup
### Mail Users
- Email address
- Mailbox size
- Message count
- Delete action
### Email Aliases
- Alias address
- Forward target
## Dependencies
- `secubox-app-mailserver` - Backend CLI
- `luci-base` - LuCI framework

View File

@ -0,0 +1,480 @@
'use strict';
'require view';
'require rpc';
'require ui';
'require form';
'require uci';
var callStatus = rpc.declare({
object: 'luci.mailserver',
method: 'status',
expect: {}
});
var callUserList = rpc.declare({
object: 'luci.mailserver',
method: 'user_list',
expect: {}
});
var callAliasList = rpc.declare({
object: 'luci.mailserver',
method: 'alias_list',
expect: {}
});
var callWebmailStatus = rpc.declare({
object: 'luci.mailserver',
method: 'webmail_status',
expect: {}
});
var callInstall = rpc.declare({
object: 'luci.mailserver',
method: 'install',
expect: {}
});
var callStart = rpc.declare({
object: 'luci.mailserver',
method: 'start',
expect: {}
});
var callStop = rpc.declare({
object: 'luci.mailserver',
method: 'stop',
expect: {}
});
var callUserAdd = rpc.declare({
object: 'luci.mailserver',
method: 'user_add',
params: ['email', 'password'],
expect: {}
});
var callUserDel = rpc.declare({
object: 'luci.mailserver',
method: 'user_del',
params: ['email'],
expect: {}
});
var callAliasAdd = rpc.declare({
object: 'luci.mailserver',
method: 'alias_add',
params: ['alias', 'target'],
expect: {}
});
var callDnsSetup = rpc.declare({
object: 'luci.mailserver',
method: 'dns_setup',
expect: {}
});
var callSslSetup = rpc.declare({
object: 'luci.mailserver',
method: 'ssl_setup',
expect: {}
});
var callWebmailConfigure = rpc.declare({
object: 'luci.mailserver',
method: 'webmail_configure',
expect: {}
});
var callMeshBackup = rpc.declare({
object: 'luci.mailserver',
method: 'mesh_backup',
expect: {}
});
return view.extend({
load: function() {
return Promise.all([
callStatus(),
callUserList(),
callAliasList(),
callWebmailStatus()
]);
},
render: function(data) {
var status = data[0] || {};
var users = (data[1] || {}).users || [];
var aliases = (data[2] || {}).aliases || [];
var webmail = data[3] || {};
var view = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, 'Mail Server'),
// Status Section
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Server Status'),
E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td', 'style': 'width:150px' }, 'Status'),
E('td', { 'class': 'td' }, [
E('span', { 'style': status.state === 'running' ? 'color:green' : 'color:red' },
status.state === 'running' ? '● Running' : '○ Stopped')
])
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, 'Domain'),
E('td', { 'class': 'td' }, status.fqdn || 'Not configured')
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, 'Users'),
E('td', { 'class': 'td' }, String(status.users || 0))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, 'Storage'),
E('td', { 'class': 'td' }, status.storage || '0')
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, 'SSL'),
E('td', { 'class': 'td' }, status.ssl_valid ? '✓ Valid' : '✗ Not configured')
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, 'Webmail'),
E('td', { 'class': 'td' }, webmail.running ? '● Running (port ' + webmail.port + ')' : '○ Stopped')
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, 'Mesh Backup'),
E('td', { 'class': 'td' }, status.mesh_enabled ? '● Enabled' : '○ Disabled')
])
]),
// Port Status
E('h4', { 'style': 'margin-top:15px' }, 'Ports'),
this.renderPortStatus(status.ports || {})
]),
// Quick Actions
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Quick Actions'),
E('div', { 'style': 'display:flex;gap:10px;flex-wrap:wrap' }, [
status.state !== 'running' ?
E('button', {
'class': 'btn cbi-button-action',
'click': ui.createHandlerFn(this, this.doStart)
}, 'Start Server') :
E('button', {
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, this.doStop)
}, 'Stop Server'),
E('button', {
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, this.doDnsSetup)
}, 'Setup DNS'),
E('button', {
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, this.doSslSetup)
}, 'Setup SSL'),
E('button', {
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, this.doWebmailConfigure)
}, 'Configure Webmail'),
E('button', {
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, this.doMeshBackup)
}, 'Mesh Backup')
])
]),
// Users Section
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Mail Users'),
E('div', { 'style': 'margin-bottom:10px' }, [
E('button', {
'class': 'btn cbi-button-add',
'click': ui.createHandlerFn(this, this.showAddUserModal)
}, 'Add User')
]),
this.renderUserTable(users)
]),
// Aliases Section
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Email Aliases'),
E('div', { 'style': 'margin-bottom:10px' }, [
E('button', {
'class': 'btn cbi-button-add',
'click': ui.createHandlerFn(this, this.showAddAliasModal)
}, 'Add Alias')
]),
this.renderAliasTable(aliases)
])
]);
return view;
},
renderPortStatus: function(ports) {
var portList = [
{ port: '25', name: 'SMTP' },
{ port: '587', name: 'Submission' },
{ port: '465', name: 'SMTPS' },
{ port: '993', name: 'IMAPS' },
{ port: '995', name: 'POP3S' }
];
return E('div', { 'style': 'display:flex;gap:15px;flex-wrap:wrap' },
portList.map(function(p) {
var isOpen = ports[p.port];
return E('span', {
'style': 'padding:5px 10px;border-radius:4px;background:' + (isOpen ? '#d4edda' : '#f8d7da')
}, p.name + ' (' + p.port + '): ' + (isOpen ? '✓' : '✗'));
})
);
},
renderUserTable: function(users) {
if (!users || users.length === 0) {
return E('p', { 'class': 'cbi-value-description' }, 'No mail users configured.');
}
var rows = users.map(L.bind(function(u) {
return E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, u.email),
E('td', { 'class': 'td' }, u.size || '0'),
E('td', { 'class': 'td' }, String(u.messages || 0)),
E('td', { 'class': 'td' }, [
E('button', {
'class': 'btn cbi-button-remove',
'style': 'padding:2px 8px;font-size:12px',
'click': ui.createHandlerFn(this, this.doDeleteUser, u.email)
}, 'Delete')
])
]);
}, this));
return E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, 'Email'),
E('th', { 'class': 'th' }, 'Size'),
E('th', { 'class': 'th' }, 'Messages'),
E('th', { 'class': 'th' }, 'Actions')
])
].concat(rows));
},
renderAliasTable: function(aliases) {
if (!aliases || aliases.length === 0) {
return E('p', { 'class': 'cbi-value-description' }, 'No aliases configured.');
}
var rows = aliases.map(function(a) {
return E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, a.alias),
E('td', { 'class': 'td' }, '→'),
E('td', { 'class': 'td' }, a.target)
]);
});
return E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, 'Alias'),
E('th', { 'class': 'th' }, ''),
E('th', { 'class': 'th' }, 'Target')
])
].concat(rows));
},
showAddUserModal: function() {
var emailInput, passwordInput;
ui.showModal('Add Mail User', [
E('p', {}, 'Enter email address and password for the new user.'),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, 'Email'),
E('div', { 'class': 'cbi-value-field' }, [
emailInput = E('input', { 'type': 'email', 'class': 'cbi-input-text', 'placeholder': 'user@domain.com' })
])
]),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, 'Password'),
E('div', { 'class': 'cbi-value-field' }, [
passwordInput = E('input', { 'type': 'password', 'class': 'cbi-input-text' })
])
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'click': ui.hideModal
}, 'Cancel'),
' ',
E('button', {
'class': 'btn cbi-button-action',
'click': ui.createHandlerFn(this, function() {
var email = emailInput.value;
var password = passwordInput.value;
if (!email || !password) {
ui.addNotification(null, E('p', 'Email and password required'), 'error');
return;
}
ui.hideModal();
return this.doAddUser(email, password);
})
}, 'Add User')
])
]);
},
showAddAliasModal: function() {
var aliasInput, targetInput;
ui.showModal('Add Email Alias', [
E('p', {}, 'Create an alias that forwards to another address.'),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, 'Alias'),
E('div', { 'class': 'cbi-value-field' }, [
aliasInput = E('input', { 'type': 'email', 'class': 'cbi-input-text', 'placeholder': 'info@domain.com' })
])
]),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, 'Forward To'),
E('div', { 'class': 'cbi-value-field' }, [
targetInput = E('input', { 'type': 'email', 'class': 'cbi-input-text', 'placeholder': 'user@domain.com' })
])
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'click': ui.hideModal
}, 'Cancel'),
' ',
E('button', {
'class': 'btn cbi-button-action',
'click': ui.createHandlerFn(this, function() {
var alias = aliasInput.value;
var target = targetInput.value;
if (!alias || !target) {
ui.addNotification(null, E('p', 'Alias and target required'), 'error');
return;
}
ui.hideModal();
return this.doAddAlias(alias, target);
})
}, 'Add Alias')
])
]);
},
doStart: function() {
ui.showModal('Starting Server', [
E('p', { 'class': 'spinning' }, 'Starting mail server...')
]);
return callStart().then(function() {
ui.hideModal();
window.location.reload();
});
},
doStop: function() {
ui.showModal('Stopping Server', [
E('p', { 'class': 'spinning' }, 'Stopping mail server...')
]);
return callStop().then(function() {
ui.hideModal();
window.location.reload();
});
},
doAddUser: function(email, password) {
ui.showModal('Adding User', [
E('p', { 'class': 'spinning' }, 'Adding user: ' + email)
]);
return callUserAdd(email, password).then(function(res) {
ui.hideModal();
if (res.code === 0) {
ui.addNotification(null, E('p', 'User added: ' + email), 'success');
} else {
ui.addNotification(null, E('p', 'Failed: ' + res.output), 'error');
}
window.location.reload();
});
},
doDeleteUser: function(email) {
if (!confirm('Delete user ' + email + '?')) return;
ui.showModal('Deleting User', [
E('p', { 'class': 'spinning' }, 'Deleting user: ' + email)
]);
return callUserDel(email).then(function() {
ui.hideModal();
window.location.reload();
});
},
doAddAlias: function(alias, target) {
ui.showModal('Adding Alias', [
E('p', { 'class': 'spinning' }, 'Adding alias: ' + alias)
]);
return callAliasAdd(alias, target).then(function() {
ui.hideModal();
window.location.reload();
});
},
doDnsSetup: function() {
ui.showModal('DNS Setup', [
E('p', { 'class': 'spinning' }, 'Creating MX, SPF, DMARC records...')
]);
return callDnsSetup().then(function(res) {
ui.hideModal();
if (res.code === 0) {
ui.addNotification(null, E('p', 'DNS records created'), 'success');
} else {
ui.addNotification(null, E('p', res.output), 'warning');
}
});
},
doSslSetup: function() {
ui.showModal('SSL Setup', [
E('p', { 'class': 'spinning' }, 'Obtaining SSL certificate via DNS-01...')
]);
return callSslSetup().then(function(res) {
ui.hideModal();
if (res.code === 0) {
ui.addNotification(null, E('p', 'SSL certificate installed'), 'success');
} else {
ui.addNotification(null, E('p', res.output), 'error');
}
window.location.reload();
});
},
doWebmailConfigure: function() {
ui.showModal('Configuring Webmail', [
E('p', { 'class': 'spinning' }, 'Configuring Roundcube...')
]);
return callWebmailConfigure().then(function(res) {
ui.hideModal();
ui.addNotification(null, E('p', 'Webmail configured. Restart webmail container to apply.'), 'success');
});
},
doMeshBackup: function() {
ui.showModal('Mesh Backup', [
E('p', { 'class': 'spinning' }, 'Creating backup for mesh sync...')
]);
return callMeshBackup().then(function(res) {
ui.hideModal();
if (res.code === 0) {
ui.addNotification(null, E('p', 'Backup created'), 'success');
} else {
ui.addNotification(null, E('p', res.output), 'error');
}
});
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -0,0 +1,330 @@
#!/bin/sh
# SecuBox Mail Server RPCD Handler
. /usr/share/libubox/jshn.sh
MAILCTL="/usr/sbin/mailctl"
CONFIG="mailserver"
case "$1" in
list)
cat <<-EOF
{
"status": {},
"user_list": {},
"alias_list": {},
"webmail_status": {},
"logs": { "lines": "number" },
"install": {},
"start": {},
"stop": {},
"restart": {},
"user_add": { "email": "string", "password": "string" },
"user_del": { "email": "string" },
"user_passwd": { "email": "string", "password": "string" },
"alias_add": { "alias": "string", "target": "string" },
"dns_setup": {},
"ssl_setup": {},
"webmail_configure": {},
"mesh_backup": {},
"mesh_sync": { "mode": "string" }
}
EOF
;;
call)
case "$2" in
status)
json_init
local enabled=$(uci -q get $CONFIG.main.enabled)
local container=$(uci -q get $CONFIG.main.container)
container="${container:-mailserver}"
local domain=$(uci -q get $CONFIG.main.domain)
local hostname=$(uci -q get $CONFIG.main.hostname)
hostname="${hostname:-mail}"
local data_path=$(uci -q get $CONFIG.main.data_path)
data_path="${data_path:-/srv/mailserver}"
# Container state
local state="stopped"
lxc-info -n "$container" 2>/dev/null | grep -q "RUNNING" && state="running"
# User count
local user_count=0
[ -f "$data_path/config/users" ] && user_count=$(wc -l < "$data_path/config/users" 2>/dev/null || echo "0")
# Storage
local storage=$(du -sh "$data_path" 2>/dev/null | awk '{print $1}')
# SSL status
local ssl_valid=0
if [ -f "$data_path/ssl/fullchain.pem" ]; then
local expiry=$(openssl x509 -in "$data_path/ssl/fullchain.pem" -noout -enddate 2>/dev/null | cut -d= -f2)
[ -n "$expiry" ] && ssl_valid=1
fi
# Webmail
local webmail_container=$(uci -q get $CONFIG.webmail.container)
webmail_container="${webmail_container:-secubox-webmail}"
local webmail_running=0
docker ps 2>/dev/null | grep -q "$webmail_container" && webmail_running=1
# Mesh
local mesh_enabled=$(uci -q get $CONFIG.mesh.enabled)
json_add_boolean "enabled" "${enabled:-0}"
json_add_string "state" "$state"
json_add_string "container" "$container"
json_add_string "domain" "${domain:-not set}"
json_add_string "hostname" "$hostname"
json_add_string "fqdn" "${hostname}.${domain}"
json_add_int "users" "$user_count"
json_add_string "storage" "${storage:-0}"
json_add_boolean "ssl_valid" "$ssl_valid"
json_add_boolean "webmail_running" "$webmail_running"
json_add_string "webmail_container" "$webmail_container"
json_add_boolean "mesh_enabled" "${mesh_enabled:-0}"
# Port status
json_add_object "ports"
for port in 25 587 465 993 995; do
if netstat -tln 2>/dev/null | grep -q ":$port "; then
json_add_boolean "$port" 1
else
json_add_boolean "$port" 0
fi
done
json_close_object
json_dump
;;
user_list)
local data_path=$(uci -q get $CONFIG.main.data_path)
data_path="${data_path:-/srv/mailserver}"
json_init
json_add_array "users"
if [ -f "$data_path/config/users" ]; then
while IFS=: read -r email hash; do
[ -z "$email" ] && continue
local domain=$(echo "$email" | cut -d@ -f2)
local user=$(echo "$email" | cut -d@ -f1)
local maildir="$data_path/mail/$domain/$user"
local size=$(du -sh "$maildir" 2>/dev/null | awk '{print $1}')
local count=$(find "$maildir" -type f 2>/dev/null | wc -l)
json_add_object ""
json_add_string "email" "$email"
json_add_string "size" "${size:-0}"
json_add_int "messages" "$count"
json_close_object
done < "$data_path/config/users"
fi
json_close_array
json_dump
;;
alias_list)
local data_path=$(uci -q get $CONFIG.main.data_path)
data_path="${data_path:-/srv/mailserver}"
json_init
json_add_array "aliases"
if [ -f "$data_path/config/valias" ]; then
while read -r alias target; do
[ -z "$alias" ] && continue
json_add_object ""
json_add_string "alias" "$alias"
json_add_string "target" "$target"
json_close_object
done < "$data_path/config/valias"
fi
json_close_array
json_dump
;;
webmail_status)
local webmail_container=$(uci -q get $CONFIG.webmail.container)
webmail_container="${webmail_container:-secubox-webmail}"
local port=$(uci -q get $CONFIG.webmail.port)
port="${port:-8026}"
json_init
if docker ps 2>/dev/null | grep -q "$webmail_container"; then
json_add_boolean "running" 1
json_add_string "container" "$webmail_container"
json_add_int "port" "$port"
json_add_string "url" "http://localhost:$port"
else
json_add_boolean "running" 0
fi
json_dump
;;
logs)
json_load "$3"
json_get_var lines lines
lines="${lines:-50}"
local container=$(uci -q get $CONFIG.main.container)
container="${container:-mailserver}"
json_init
local log_output=$(lxc-attach -n "$container" -- tail -n "$lines" /var/log/mail.log 2>/dev/null)
json_add_string "logs" "$log_output"
json_dump
;;
install)
json_init
local output=$($MAILCTL install 2>&1)
local rc=$?
json_add_int "code" "$rc"
json_add_string "output" "$output"
json_dump
;;
start)
json_init
$MAILCTL start 2>&1
json_add_int "code" "$?"
json_dump
;;
stop)
json_init
$MAILCTL stop 2>&1
json_add_int "code" "$?"
json_dump
;;
restart)
json_init
$MAILCTL restart 2>&1
json_add_int "code" "$?"
json_dump
;;
user_add)
json_load "$3"
json_get_var email email
json_get_var password password
json_init
if [ -z "$email" ]; then
json_add_int "code" 1
json_add_string "error" "Email required"
else
local output=$($MAILCTL user add "$email" "$password" 2>&1)
json_add_int "code" "$?"
json_add_string "output" "$output"
fi
json_dump
;;
user_del)
json_load "$3"
json_get_var email email
json_init
if [ -z "$email" ]; then
json_add_int "code" 1
json_add_string "error" "Email required"
else
local output=$($MAILCTL user del "$email" 2>&1)
json_add_int "code" "$?"
json_add_string "output" "$output"
fi
json_dump
;;
user_passwd)
json_load "$3"
json_get_var email email
json_get_var password password
json_init
if [ -z "$email" ] || [ -z "$password" ]; then
json_add_int "code" 1
json_add_string "error" "Email and password required"
else
local output=$($MAILCTL user passwd "$email" "$password" 2>&1)
json_add_int "code" "$?"
json_add_string "output" "$output"
fi
json_dump
;;
alias_add)
json_load "$3"
json_get_var alias alias
json_get_var target target
json_init
if [ -z "$alias" ] || [ -z "$target" ]; then
json_add_int "code" 1
json_add_string "error" "Alias and target required"
else
local output=$($MAILCTL alias add "$alias" "$target" 2>&1)
json_add_int "code" "$?"
json_add_string "output" "$output"
fi
json_dump
;;
dns_setup)
json_init
local output=$($MAILCTL dns-setup 2>&1)
json_add_int "code" "$?"
json_add_string "output" "$output"
json_dump
;;
ssl_setup)
json_init
local output=$($MAILCTL ssl-setup 2>&1)
json_add_int "code" "$?"
json_add_string "output" "$output"
json_dump
;;
webmail_configure)
json_init
local output=$($MAILCTL webmail configure 2>&1)
json_add_int "code" "$?"
json_add_string "output" "$output"
json_dump
;;
mesh_backup)
json_init
local output=$($MAILCTL mesh backup 2>&1)
json_add_int "code" "$?"
json_add_string "output" "$output"
json_dump
;;
mesh_sync)
json_load "$3"
json_get_var mode mode
mode="${mode:-push}"
json_init
local output=$($MAILCTL mesh sync "$mode" 2>&1)
json_add_int "code" "$?"
json_add_string "output" "$output"
json_dump
;;
esac
;;
esac
exit 0

View File

@ -0,0 +1,14 @@
{
"admin/services/mailserver": {
"title": "Mail Server",
"order": 60,
"action": {
"type": "view",
"path": "mailserver/overview"
},
"depends": {
"acl": ["luci-app-mailserver"],
"uci": { "mailserver": true }
}
}
}

View File

@ -0,0 +1,17 @@
{
"luci-app-mailserver": {
"description": "Grant access to Mail Server Manager",
"read": {
"ubus": {
"luci.mailserver": ["status", "user_list", "alias_list", "webmail_status", "logs"]
},
"uci": ["mailserver"]
},
"write": {
"ubus": {
"luci.mailserver": ["install", "start", "stop", "restart", "user_add", "user_del", "user_passwd", "alias_add", "dns_setup", "ssl_setup", "webmail_configure", "mesh_backup", "mesh_sync"]
},
"uci": ["mailserver"]
}
}
}