From ded107e408f1945da6834b2bb084693beefe06f0 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Thu, 5 Feb 2026 10:43:55 +0100 Subject: [PATCH] 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 --- package/secubox/luci-app-mailserver/Makefile | 29 ++ package/secubox/luci-app-mailserver/README.md | 81 +++ .../resources/view/mailserver/overview.js | 480 ++++++++++++++++++ .../root/usr/libexec/rpcd/luci.mailserver | 330 ++++++++++++ .../luci/menu.d/luci-app-mailserver.json | 14 + .../share/rpcd/acl.d/luci-app-mailserver.json | 17 + 6 files changed, 951 insertions(+) create mode 100644 package/secubox/luci-app-mailserver/Makefile create mode 100644 package/secubox/luci-app-mailserver/README.md create mode 100644 package/secubox/luci-app-mailserver/htdocs/luci-static/resources/view/mailserver/overview.js create mode 100644 package/secubox/luci-app-mailserver/root/usr/libexec/rpcd/luci.mailserver create mode 100644 package/secubox/luci-app-mailserver/root/usr/share/luci/menu.d/luci-app-mailserver.json create mode 100644 package/secubox/luci-app-mailserver/root/usr/share/rpcd/acl.d/luci-app-mailserver.json diff --git a/package/secubox/luci-app-mailserver/Makefile b/package/secubox/luci-app-mailserver/Makefile new file mode 100644 index 00000000..b219af70 --- /dev/null +++ b/package/secubox/luci-app-mailserver/Makefile @@ -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))) diff --git a/package/secubox/luci-app-mailserver/README.md b/package/secubox/luci-app-mailserver/README.md new file mode 100644 index 00000000..65dcf31e --- /dev/null +++ b/package/secubox/luci-app-mailserver/README.md @@ -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 diff --git a/package/secubox/luci-app-mailserver/htdocs/luci-static/resources/view/mailserver/overview.js b/package/secubox/luci-app-mailserver/htdocs/luci-static/resources/view/mailserver/overview.js new file mode 100644 index 00000000..3013d3f7 --- /dev/null +++ b/package/secubox/luci-app-mailserver/htdocs/luci-static/resources/view/mailserver/overview.js @@ -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 +}); diff --git a/package/secubox/luci-app-mailserver/root/usr/libexec/rpcd/luci.mailserver b/package/secubox/luci-app-mailserver/root/usr/libexec/rpcd/luci.mailserver new file mode 100644 index 00000000..531f14e8 --- /dev/null +++ b/package/secubox/luci-app-mailserver/root/usr/libexec/rpcd/luci.mailserver @@ -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 diff --git a/package/secubox/luci-app-mailserver/root/usr/share/luci/menu.d/luci-app-mailserver.json b/package/secubox/luci-app-mailserver/root/usr/share/luci/menu.d/luci-app-mailserver.json new file mode 100644 index 00000000..17789720 --- /dev/null +++ b/package/secubox/luci-app-mailserver/root/usr/share/luci/menu.d/luci-app-mailserver.json @@ -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 } + } + } +} diff --git a/package/secubox/luci-app-mailserver/root/usr/share/rpcd/acl.d/luci-app-mailserver.json b/package/secubox/luci-app-mailserver/root/usr/share/rpcd/acl.d/luci-app-mailserver.json new file mode 100644 index 00000000..a1a893e5 --- /dev/null +++ b/package/secubox/luci-app-mailserver/root/usr/share/rpcd/acl.d/luci-app-mailserver.json @@ -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"] + } + } +}