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:
parent
c6fb79ed3b
commit
ded107e408
29
package/secubox/luci-app-mailserver/Makefile
Normal file
29
package/secubox/luci-app-mailserver/Makefile
Normal 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)))
|
||||
81
package/secubox/luci-app-mailserver/README.md
Normal file
81
package/secubox/luci-app-mailserver/README.md
Normal 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
|
||||
@ -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
|
||||
});
|
||||
@ -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
|
||||
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user