From 58fe0909ace9465a66f16f8342d656d7a1c2ca0e Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Thu, 5 Feb 2026 10:52:03 +0100 Subject: [PATCH] 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 --- package/secubox/luci-app-mailinabox/Makefile | 32 - package/secubox/luci-app-mailinabox/README.md | 41 - .../resources/view/mailinabox/overview.js | 103 --- .../resources/view/mailinabox/settings.js | 75 -- .../root/usr/libexec/rpcd/luci.mailinabox | 139 ---- .../luci/menu.d/luci-app-mailinabox.json | 28 - .../share/rpcd/acl.d/luci-app-mailinabox.json | 17 - .../secubox/secubox-app-mailinabox/Makefile | 57 -- .../secubox/secubox-app-mailinabox/README.md | 52 -- .../files/etc/config/mailinabox | 30 - .../files/etc/init.d/mailinabox | 42 - .../files/usr/sbin/mailinaboxctl | 725 ------------------ 12 files changed, 1341 deletions(-) delete mode 100644 package/secubox/luci-app-mailinabox/Makefile delete mode 100644 package/secubox/luci-app-mailinabox/README.md delete mode 100644 package/secubox/luci-app-mailinabox/htdocs/luci-static/resources/view/mailinabox/overview.js delete mode 100644 package/secubox/luci-app-mailinabox/htdocs/luci-static/resources/view/mailinabox/settings.js delete mode 100755 package/secubox/luci-app-mailinabox/root/usr/libexec/rpcd/luci.mailinabox delete mode 100644 package/secubox/luci-app-mailinabox/root/usr/share/luci/menu.d/luci-app-mailinabox.json delete mode 100644 package/secubox/luci-app-mailinabox/root/usr/share/rpcd/acl.d/luci-app-mailinabox.json delete mode 100644 package/secubox/secubox-app-mailinabox/Makefile delete mode 100644 package/secubox/secubox-app-mailinabox/README.md delete mode 100644 package/secubox/secubox-app-mailinabox/files/etc/config/mailinabox delete mode 100755 package/secubox/secubox-app-mailinabox/files/etc/init.d/mailinabox delete mode 100755 package/secubox/secubox-app-mailinabox/files/usr/sbin/mailinaboxctl diff --git a/package/secubox/luci-app-mailinabox/Makefile b/package/secubox/luci-app-mailinabox/Makefile deleted file mode 100644 index afe24566..00000000 --- a/package/secubox/luci-app-mailinabox/Makefile +++ /dev/null @@ -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 -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))) diff --git a/package/secubox/luci-app-mailinabox/README.md b/package/secubox/luci-app-mailinabox/README.md deleted file mode 100644 index 604cfe33..00000000 --- a/package/secubox/luci-app-mailinabox/README.md +++ /dev/null @@ -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 diff --git a/package/secubox/luci-app-mailinabox/htdocs/luci-static/resources/view/mailinabox/overview.js b/package/secubox/luci-app-mailinabox/htdocs/luci-static/resources/view/mailinabox/overview.js deleted file mode 100644 index 68a536ec..00000000 --- a/package/secubox/luci-app-mailinabox/htdocs/luci-static/resources/view/mailinabox/overview.js +++ /dev/null @@ -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 -}); diff --git a/package/secubox/luci-app-mailinabox/htdocs/luci-static/resources/view/mailinabox/settings.js b/package/secubox/luci-app-mailinabox/htdocs/luci-static/resources/view/mailinabox/settings.js deleted file mode 100644 index 52e1e009..00000000 --- a/package/secubox/luci-app-mailinabox/htdocs/luci-static/resources/view/mailinabox/settings.js +++ /dev/null @@ -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(); - } -}); diff --git a/package/secubox/luci-app-mailinabox/root/usr/libexec/rpcd/luci.mailinabox b/package/secubox/luci-app-mailinabox/root/usr/libexec/rpcd/luci.mailinabox deleted file mode 100755 index 38c1d1a3..00000000 --- a/package/secubox/luci-app-mailinabox/root/usr/libexec/rpcd/luci.mailinabox +++ /dev/null @@ -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 </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 diff --git a/package/secubox/luci-app-mailinabox/root/usr/share/luci/menu.d/luci-app-mailinabox.json b/package/secubox/luci-app-mailinabox/root/usr/share/luci/menu.d/luci-app-mailinabox.json deleted file mode 100644 index bf048d7d..00000000 --- a/package/secubox/luci-app-mailinabox/root/usr/share/luci/menu.d/luci-app-mailinabox.json +++ /dev/null @@ -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" - } - } -} diff --git a/package/secubox/luci-app-mailinabox/root/usr/share/rpcd/acl.d/luci-app-mailinabox.json b/package/secubox/luci-app-mailinabox/root/usr/share/rpcd/acl.d/luci-app-mailinabox.json deleted file mode 100644 index dfc8c29e..00000000 --- a/package/secubox/luci-app-mailinabox/root/usr/share/rpcd/acl.d/luci-app-mailinabox.json +++ /dev/null @@ -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"] - } - } -} diff --git a/package/secubox/secubox-app-mailinabox/Makefile b/package/secubox/secubox-app-mailinabox/Makefile deleted file mode 100644 index 4567a5a2..00000000 --- a/package/secubox/secubox-app-mailinabox/Makefile +++ /dev/null @@ -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 -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)) diff --git a/package/secubox/secubox-app-mailinabox/README.md b/package/secubox/secubox-app-mailinabox/README.md deleted file mode 100644 index 1c9e0630..00000000 --- a/package/secubox/secubox-app-mailinabox/README.md +++ /dev/null @@ -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 # Add mail user -mailinaboxctl user list # List mail users -mailinaboxctl user del # 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 diff --git a/package/secubox/secubox-app-mailinabox/files/etc/config/mailinabox b/package/secubox/secubox-app-mailinabox/files/etc/config/mailinabox deleted file mode 100644 index f72bb117..00000000 --- a/package/secubox/secubox-app-mailinabox/files/etc/config/mailinabox +++ /dev/null @@ -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' diff --git a/package/secubox/secubox-app-mailinabox/files/etc/init.d/mailinabox b/package/secubox/secubox-app-mailinabox/files/etc/init.d/mailinabox deleted file mode 100755 index ed73f0e5..00000000 --- a/package/secubox/secubox-app-mailinabox/files/etc/init.d/mailinabox +++ /dev/null @@ -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 -} diff --git a/package/secubox/secubox-app-mailinabox/files/usr/sbin/mailinaboxctl b/package/secubox/secubox-app-mailinabox/files/usr/sbin/mailinaboxctl deleted file mode 100755 index 21badf75..00000000 --- a/package/secubox/secubox-app-mailinabox/files/usr/sbin/mailinaboxctl +++ /dev/null @@ -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 - -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 [password] Add email account - user-del Remove email account - user-list List all email accounts - user-passwd Change user password - alias-add Add email alias - alias-del Remove email alias - alias-list List all aliases - -Domain & SSL: - domain-add 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 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 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 [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 "; 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 "; 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 "; return 1; } - docker_setup alias add "$alias" "$target" -} - -cmd_alias_del() { - local alias="$1" - [ -z "$alias" ] && { log_error "Usage: mailinaboxctl alias-del "; 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 "; 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 " - 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 "; 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