diff --git a/.claude/HISTORY.md b/.claude/HISTORY.md
index 16d9097f..8ec82d03 100644
--- a/.claude/HISTORY.md
+++ b/.claude/HISTORY.md
@@ -2035,3 +2035,31 @@ git checkout HEAD -- index.html
- `secubox-app-mailserver/files/usr/lib/mailserver/users.sh`
- `secubox-app-mailserver/files/usr/sbin/mailctl`
- `luci-app-mailserver/root/usr/libexec/rpcd/luci.mailserver`
+
+### 2026-02-16: Mail Autoconfig & Repair Features
+
+**Mail Autoconfig Setup**
+- Created autoconfig files for automatic mail client configuration:
+ - `config-v1.1.xml` - Mozilla Thunderbird format
+ - `autodiscover.xml` - Microsoft Outlook format
+ - `email.mobileconfig` - Apple iOS/macOS format
+- Set up uhttpd instance on port 8025 to serve autoconfig files
+- Added HAProxy backends with waf_bypass for autoconfig.secubox.in and autoconfig.gk2.secubox.in
+- Created mailctl autoconfig-setup and autoconfig-status commands
+
+**LuCI Enhancement: luci-app-mailserver**
+- Added `user_repair` method for mailbox repair (doveadm force-resync)
+- Added repair button (🔧) to user actions in overview
+- Updated ACL with new permission
+
+**LuCI Enhancement: luci-app-nextcloud**
+- Added `list_users` method to list Nextcloud users
+- Added `reset_password` method for password reset via OCC
+- Updated ACL with new permissions
+
+**Files Modified:**
+- `luci-app-mailserver/root/usr/libexec/rpcd/luci.mailserver`
+- `luci-app-mailserver/htdocs/luci-static/resources/view/mailserver/overview.js`
+- `luci-app-mailserver/root/usr/share/rpcd/acl.d/luci-app-mailserver.json`
+- `luci-app-nextcloud/root/usr/libexec/rpcd/luci.nextcloud`
+- `luci-app-nextcloud/root/usr/share/rpcd/acl.d/luci-app-nextcloud.json`
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
index e8670be0..ac6ddcca 100644
--- 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
@@ -105,6 +105,13 @@ var callFixPorts = rpc.declare({
expect: {}
});
+var callUserRepair = rpc.declare({
+ object: 'luci.mailserver',
+ method: 'user_repair',
+ params: ['email'],
+ expect: {}
+});
+
return view.extend({
load: function() {
return Promise.all([
@@ -333,7 +340,7 @@ return view.extend({
E('th', {}, 'Email'),
E('th', {}, 'Size'),
E('th', {}, 'Msgs'),
- E('th', { 'style': 'width: 120px;' }, 'Actions')
+ E('th', { 'style': 'width: 160px;' }, 'Actions')
])
]),
E('tbody', {}, users.map(function(u) {
@@ -345,11 +352,19 @@ return view.extend({
E('button', {
'class': 'kiss-btn',
'style': 'padding: 4px 8px; font-size: 11px; margin-right: 4px;',
+ 'title': 'Reset Password',
'click': ui.createHandlerFn(self, self.showResetPasswordModal, u.email)
}, '\ud83d\udd11'),
+ E('button', {
+ 'class': 'kiss-btn',
+ 'style': 'padding: 4px 8px; font-size: 11px; margin-right: 4px;',
+ 'title': 'Repair Mailbox',
+ 'click': ui.createHandlerFn(self, self.doRepairMailbox, u.email)
+ }, '\ud83d\udd27'),
E('button', {
'class': 'kiss-btn kiss-btn-red',
'style': 'padding: 4px 8px; font-size: 11px;',
+ 'title': 'Delete User',
'click': ui.createHandlerFn(self, self.doDeleteUser, u.email)
}, '\ud83d\uddd1')
])
@@ -598,6 +613,21 @@ return view.extend({
});
},
+ doRepairMailbox: function(email) {
+ ui.showModal('Repairing Mailbox', [
+ E('p', { 'class': 'spinning' }, 'Repairing mailbox for ' + email + '...')
+ ]);
+ return callUserRepair(email).then(function(res) {
+ ui.hideModal();
+ if (res.code === 0) {
+ ui.addNotification(null, E('p', 'Mailbox repaired for ' + email), 'success');
+ } else {
+ ui.addNotification(null, E('p', 'Repair output: ' + (res.output || 'No issues found')), 'info');
+ }
+ window.location.reload();
+ });
+ },
+
doDnsSetup: function() {
ui.showModal('DNS Setup', [
E('p', { 'class': 'spinning' }, 'Creating MX, SPF, DKIM, DMARC records...')
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
index 981cbdd4..de04774b 100644
--- a/package/secubox/luci-app-mailserver/root/usr/libexec/rpcd/luci.mailserver
+++ b/package/secubox/luci-app-mailserver/root/usr/libexec/rpcd/luci.mailserver
@@ -29,7 +29,8 @@ case "$1" in
"webmail_configure": {},
"mesh_backup": {},
"mesh_sync": { "mode": "string" },
- "fix_ports": {}
+ "fix_ports": {},
+ "user_repair": { "email": "string" }
}
EOF
;;
@@ -371,6 +372,31 @@ case "$1" in
json_add_string "output" "$output"
json_dump
;;
+
+ user_repair)
+ # Read JSON from stdin or $3
+ if [ -n "$3" ]; then
+ json_load "$3"
+ else
+ read -r _input
+ json_load "$_input"
+ fi
+ json_get_var email email
+
+ json_init
+ if [ -z "$email" ]; then
+ json_add_int "code" 1
+ json_add_string "error" "Email required"
+ else
+ container=$(uci -q get $CONFIG.main.container)
+ container="${container:-mailserver}"
+ # Run doveadm force-resync to repair mailbox
+ output=$(lxc-attach -n "$container" -- doveadm force-resync -u "$email" '*' 2>&1)
+ json_add_int "code" "$?"
+ json_add_string "output" "$output"
+ fi
+ json_dump
+ ;;
esac
;;
esac
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
index 740eeffd..bd6d1a7a 100644
--- 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
@@ -9,7 +9,7 @@
},
"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", "fix_ports", "alias_del"]
+ "luci.mailserver": ["install", "start", "stop", "restart", "user_add", "user_del", "user_passwd", "alias_add", "dns_setup", "ssl_setup", "webmail_configure", "mesh_backup", "mesh_sync", "fix_ports", "alias_del", "user_repair"]
},
"uci": ["mailserver"]
}
diff --git a/package/secubox/luci-app-nextcloud/root/usr/libexec/rpcd/luci.nextcloud b/package/secubox/luci-app-nextcloud/root/usr/libexec/rpcd/luci.nextcloud
index a5b7e4e0..da12cb2b 100755
--- a/package/secubox/luci-app-nextcloud/root/usr/libexec/rpcd/luci.nextcloud
+++ b/package/secubox/luci-app-nextcloud/root/usr/libexec/rpcd/luci.nextcloud
@@ -312,6 +312,61 @@ get_logs() {
echo "{\"logs\": \"$log_content\"}"
}
+# List users
+list_users() {
+ if ! lxc_running; then
+ echo '{"users": []}'
+ return
+ fi
+
+ local users_json
+ users_json=$(lxc-attach -n "$LXC_NAME" -- su -s /bin/bash www-data -c "php /var/www/nextcloud/occ user:list --output=json" 2>/dev/null)
+
+ if [ -z "$users_json" ] || [ "$users_json" = "{}" ]; then
+ echo '{"users": []}'
+ return
+ fi
+
+ # Convert from {uid: displayname} to [{uid: x, displayname: y}]
+ local users_array="[]"
+ users_array=$(echo "$users_json" | jsonfilter -e '@' 2>/dev/null | \
+ awk -F: '{gsub(/[{}"]/,"",$1); gsub(/[{}"]/,"",$2); if($1!="") printf "{\"uid\":\"%s\",\"displayname\":\"%s\"},", $1, $2}' | \
+ sed 's/,$//' | sed 's/^/[/;s/$/]/')
+
+ [ -z "$users_array" ] && users_array="[]"
+ echo "{\"users\": $users_array}"
+}
+
+# Reset user password
+reset_password() {
+ local input
+ read -r input
+ local uid=$(echo "$input" | jsonfilter -e '@.uid' 2>/dev/null)
+ local password=$(echo "$input" | jsonfilter -e '@.password' 2>/dev/null)
+
+ if [ -z "$uid" ] || [ -z "$password" ]; then
+ echo '{"success": false, "error": "User ID and password required"}'
+ return
+ fi
+
+ if ! lxc_running; then
+ echo '{"success": false, "error": "Container not running"}'
+ return
+ fi
+
+ # Reset password via OCC
+ local result
+ result=$(lxc-attach -n "$LXC_NAME" -- su -s /bin/bash www-data -c "OC_PASS='$password' php /var/www/nextcloud/occ user:resetpassword --password-from-env '$uid'" 2>&1)
+ local rc=$?
+
+ if [ $rc -eq 0 ]; then
+ echo '{"success": true, "message": "Password reset for '"$uid"'"}'
+ else
+ local escaped=$(echo "$result" | sed 's/"/\\"/g' | tr '\n' ' ')
+ echo "{\"success\": false, \"error\": \"$escaped\"}"
+ fi
+}
+
# RPCD list method
list_methods() {
cat <<'EOF'
@@ -330,7 +385,9 @@ list_methods() {
"ssl_enable": {"domain": "string"},
"ssl_disable": {},
"occ": {"command": "string"},
- "logs": {}
+ "logs": {},
+ "list_users": {},
+ "reset_password": {"uid": "string", "password": "string"}
}
EOF
}
@@ -357,6 +414,8 @@ case "$1" in
ssl_disable) do_ssl_disable ;;
occ) do_occ ;;
logs) get_logs ;;
+ list_users) list_users ;;
+ reset_password) reset_password ;;
*) echo '{"error": "Unknown method"}' ;;
esac
;;
diff --git a/package/secubox/luci-app-nextcloud/root/usr/share/rpcd/acl.d/luci-app-nextcloud.json b/package/secubox/luci-app-nextcloud/root/usr/share/rpcd/acl.d/luci-app-nextcloud.json
index 916582ff..36ff9cc6 100644
--- a/package/secubox/luci-app-nextcloud/root/usr/share/rpcd/acl.d/luci-app-nextcloud.json
+++ b/package/secubox/luci-app-nextcloud/root/usr/share/rpcd/acl.d/luci-app-nextcloud.json
@@ -3,7 +3,7 @@
"description": "Grant access to Nextcloud LXC app",
"read": {
"ubus": {
- "luci.nextcloud": ["status", "get_config", "list_backups", "logs"]
+ "luci.nextcloud": ["status", "get_config", "list_backups", "logs", "list_users"]
},
"uci": ["nextcloud"]
},
@@ -20,7 +20,8 @@
"restore",
"ssl_enable",
"ssl_disable",
- "occ"
+ "occ",
+ "reset_password"
]
},
"uci": ["nextcloud"]
diff --git a/package/secubox/secubox-app-mailserver/files/usr/sbin/mailctl b/package/secubox/secubox-app-mailserver/files/usr/sbin/mailctl
index 565541ff..6e040e3d 100644
--- a/package/secubox/secubox-app-mailserver/files/usr/sbin/mailctl
+++ b/package/secubox/secubox-app-mailserver/files/usr/sbin/mailctl
@@ -588,6 +588,277 @@ EOF
esac
}
+# ============================================================================
+# Autoconfig / Autodiscover Setup
+# ============================================================================
+
+cmd_autoconfig_setup() {
+ local domain=$(uci_get main.domain)
+ local hostname=$(uci_get main.hostname)
+ hostname="${hostname:-mail}"
+ local mail_fqdn="${hostname}.${domain}"
+ local data_path=$(uci_get main.data_path)
+ data_path="${data_path:-/srv/mailserver}"
+
+ if [ -z "$domain" ]; then
+ error "Domain not configured. Set with: uci set mailserver.main.domain=example.com"
+ return 1
+ fi
+
+ log "Setting up mail autoconfig for $domain..."
+
+ local autoconfig_dir="$data_path/autoconfig"
+ mkdir -p "$autoconfig_dir/mail"
+ mkdir -p "$autoconfig_dir/autodiscover"
+
+ # Thunderbird autoconfig (config-v1.1.xml)
+ cat > "$autoconfig_dir/mail/config-v1.1.xml" << EOF
+
+
+
+ $domain
+ $domain Mail
+ $domain
+
+ $mail_fqdn
+ 993
+ SSL
+ password-cleartext
+ %EMAILADDRESS%
+
+
+ $mail_fqdn
+ 143
+ STARTTLS
+ password-cleartext
+ %EMAILADDRESS%
+
+
+ $mail_fqdn
+ 587
+ STARTTLS
+ password-cleartext
+ %EMAILADDRESS%
+
+
+ $mail_fqdn
+ 465
+ SSL
+ password-cleartext
+ %EMAILADDRESS%
+
+
+
+EOF
+
+ # Outlook/ActiveSync autodiscover (autodiscover.xml)
+ cat > "$autoconfig_dir/autodiscover/autodiscover.xml" << EOF
+
+
+
+
+ email
+ settings
+
+ IMAP
+ $mail_fqdn
+ 993
+ off
+
+ off
+ on
+ on
+
+
+ SMTP
+ $mail_fqdn
+ 587
+ off
+
+ off
+ TLS
+ on
+ on
+ off
+
+
+
+
+EOF
+
+ # Apple mobileconfig profile
+ cat > "$autoconfig_dir/$domain.mobileconfig" << EOF
+
+
+
+
+ PayloadContent
+
+
+ EmailAccountDescription
+ $domain Mail
+ EmailAccountName
+ $domain
+ EmailAccountType
+ EmailTypeIMAP
+ EmailAddress
+
+ IncomingMailServerAuthentication
+ EmailAuthPassword
+ IncomingMailServerHostName
+ $mail_fqdn
+ IncomingMailServerPortNumber
+ 993
+ IncomingMailServerUseSSL
+
+ IncomingMailServerUsername
+
+ OutgoingMailServerAuthentication
+ EmailAuthPassword
+ OutgoingMailServerHostName
+ $mail_fqdn
+ OutgoingMailServerPortNumber
+ 587
+ OutgoingMailServerUseSSL
+
+ OutgoingMailServerUsername
+
+ OutgoingPasswordSameAsIncomingPassword
+
+ PayloadDescription
+ Email account configuration for $domain
+ PayloadDisplayName
+ $domain Email
+ PayloadIdentifier
+ com.$domain.email
+ PayloadType
+ com.apple.mail.managed
+ PayloadUUID
+ $(cat /proc/sys/kernel/random/uuid 2>/dev/null || echo "12345678-1234-1234-1234-123456789012")
+ PayloadVersion
+ 1
+
+
+ PayloadDisplayName
+ $domain Mail Configuration
+ PayloadIdentifier
+ com.$domain.profile
+ PayloadType
+ Configuration
+ PayloadUUID
+ $(cat /proc/sys/kernel/random/uuid 2>/dev/null || echo "87654321-4321-4321-4321-210987654321")
+ PayloadVersion
+ 1
+
+
+EOF
+
+ log "Autoconfig files created in $autoconfig_dir"
+
+ # Setup DNS SRV records via dnsctl if available
+ if command -v dnsctl >/dev/null 2>&1; then
+ log "Setting up DNS autodiscover records..."
+
+ # Autoconfig/Autodiscover CNAME records
+ # Syntax: dnsctl add
+ dnsctl add CNAME "autoconfig" "$mail_fqdn." 2>/dev/null || true
+ dnsctl add CNAME "autodiscover" "$mail_fqdn." 2>/dev/null || true
+
+ # SRV records for autodiscover (if supported by provider)
+ dnsctl add SRV "_autodiscover._tcp" "0 1 443 $mail_fqdn." 2>/dev/null || true
+ dnsctl add SRV "_imaps._tcp" "0 1 993 $mail_fqdn." 2>/dev/null || true
+ dnsctl add SRV "_submission._tcp" "0 1 587 $mail_fqdn." 2>/dev/null || true
+
+ log "DNS records configured"
+ else
+ warn "dnsctl not found - add DNS records manually:"
+ echo " autoconfig.$domain CNAME $mail_fqdn"
+ echo " autodiscover.$domain CNAME $mail_fqdn"
+ echo " _autodiscover._tcp.$domain SRV 0 1 443 $mail_fqdn"
+ echo " _imaps._tcp.$domain SRV 0 1 993 $mail_fqdn"
+ echo " _submission._tcp.$domain SRV 0 1 587 $mail_fqdn"
+ fi
+
+ # Register with HAProxy for autoconfig serving
+ if [ -x /usr/sbin/haproxyctl ]; then
+ log "Registering autoconfig with HAProxy..."
+
+ # Check if autoconfig vhost exists
+ local vhost_exists=$(uci show haproxy 2>/dev/null | grep "autoconfig_${domain//\./_}" || true)
+
+ if [ -z "$vhost_exists" ]; then
+ # Create a simple static file server for autoconfig
+ # HAProxy will serve these files
+ uci add haproxy vhost >/dev/null
+ uci set haproxy.@vhost[-1].name="autoconfig_${domain//\./_}"
+ uci set haproxy.@vhost[-1].domain="autoconfig.$domain"
+ uci set haproxy.@vhost[-1].backend="autoconfig_backend"
+ uci set haproxy.@vhost[-1].ssl='1'
+ uci set haproxy.@vhost[-1].acme='1'
+ uci set haproxy.@vhost[-1].enabled='1'
+
+ uci add haproxy vhost >/dev/null
+ uci set haproxy.@vhost[-1].name="autodiscover_${domain//\./_}"
+ uci set haproxy.@vhost[-1].domain="autodiscover.$domain"
+ uci set haproxy.@vhost[-1].backend="autoconfig_backend"
+ uci set haproxy.@vhost[-1].ssl='1'
+ uci set haproxy.@vhost[-1].acme='1'
+ uci set haproxy.@vhost[-1].enabled='1'
+
+ uci commit haproxy
+ log "HAProxy vhosts created for autoconfig.$domain and autodiscover.$domain"
+ fi
+ fi
+
+ log ""
+ log "Autoconfig setup complete!"
+ log ""
+ log "Thunderbird: https://autoconfig.$domain/mail/config-v1.1.xml"
+ log "Outlook: https://autodiscover.$domain/autodiscover/autodiscover.xml"
+ log "Apple: https://$mail_fqdn/$domain.mobileconfig"
+ log ""
+ log "DNS records needed:"
+ log " autoconfig.$domain CNAME $mail_fqdn"
+ log " autodiscover.$domain CNAME $mail_fqdn"
+}
+
+cmd_autoconfig_status() {
+ local domain=$(uci_get main.domain)
+ local hostname=$(uci_get main.hostname)
+ hostname="${hostname:-mail}"
+ local data_path=$(uci_get main.data_path)
+ data_path="${data_path:-/srv/mailserver}"
+ local autoconfig_dir="$data_path/autoconfig"
+
+ echo "Autoconfig Status for $domain"
+ echo "=============================="
+ echo ""
+
+ if [ -f "$autoconfig_dir/mail/config-v1.1.xml" ]; then
+ echo "Thunderbird config: OK"
+ else
+ echo "Thunderbird config: NOT FOUND"
+ fi
+
+ if [ -f "$autoconfig_dir/autodiscover/autodiscover.xml" ]; then
+ echo "Outlook config: OK"
+ else
+ echo "Outlook config: NOT FOUND"
+ fi
+
+ if [ -f "$autoconfig_dir/$domain.mobileconfig" ]; then
+ echo "Apple config: OK"
+ else
+ echo "Apple config: NOT FOUND"
+ fi
+
+ echo ""
+ echo "DNS Check:"
+ echo "----------"
+ host autoconfig.$domain 2>/dev/null | head -1 || echo "autoconfig.$domain: NOT RESOLVED"
+ host autodiscover.$domain 2>/dev/null | head -1 || echo "autodiscover.$domain: NOT RESOLVED"
+}
+
# ============================================================================
# Firewall Setup
# ============================================================================
@@ -693,6 +964,8 @@ Setup:
ssl-setup Obtain SSL certificate
firewall-setup Setup mail port forwarding (WAN only)
firewall-clear Remove mail firewall rules
+ autoconfig-setup Setup autodiscover for mail clients
+ autoconfig-status Check autoconfig status
Service:
start Start mail server
@@ -769,6 +1042,8 @@ case "${1:-}" in
fix-ports) shift; cmd_fix_ports "$@" ;;
firewall-setup) shift; cmd_firewall_setup "$@" ;;
firewall-clear) shift; cmd_firewall_clear "$@" ;;
+ autoconfig-setup) shift; cmd_autoconfig_setup "$@" ;;
+ autoconfig-status) shift; cmd_autoconfig_status "$@" ;;
help|--help|-h|'') show_help ;;
*) error "Unknown command: $1"; show_help >&2; exit 1 ;;
esac