From 2bb40d9419167770ac197337ade458f140dd36b1 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Wed, 4 Mar 2026 10:16:07 +0100 Subject: [PATCH] fix(users,routing): Add gitea/jellyfin support and fix mitmproxy routes secubox-users: - Add gitea and jellyfin to supported services list - Add create/update/delete handlers for gitea (via API) and jellyfin - Update CLI help and status display to include new services luci-app-secubox-users: - Add jellyfin service checkbox and badge in frontend - Update RPCD handler to check jellyfin service status mitmproxy routing fix: - nextcloudctl: Use host LAN IP instead of 127.0.0.1 for WAF routes (mitmproxy runs in container, can't reach host's localhost) - metablogizerctl: Same fix for mitmproxy route registration - mitmproxyctl: Fix sync_metablogizer_routes to use host IP This fixes 502/403 errors when accessing services through HAProxy->mitmproxy because the mitmproxy container couldn't route to 127.0.0.1 on the host. Co-Authored-By: Claude Opus 4.5 --- .../resources/view/secubox-users/overview.js | 7 +- .../root/usr/libexec/rpcd/luci.secubox-users | 5 +- .../files/usr/sbin/metablogizerctl | 10 ++- .../files/usr/sbin/mitmproxyctl | 10 ++- .../files/usr/sbin/nextcloudctl | 16 ++-- .../files/usr/sbin/secubox-users | 88 ++++++++++++++++++- 6 files changed, 119 insertions(+), 17 deletions(-) diff --git a/package/secubox/luci-app-secubox-users/htdocs/luci-static/resources/view/secubox-users/overview.js b/package/secubox/luci-app-secubox-users/htdocs/luci-static/resources/view/secubox-users/overview.js index b85f65a8..0f8b0ccb 100644 --- a/package/secubox/luci-app-secubox-users/htdocs/luci-static/resources/view/secubox-users/overview.js +++ b/package/secubox/luci-app-secubox-users/htdocs/luci-static/resources/view/secubox-users/overview.js @@ -117,7 +117,8 @@ return view.extend({ E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-jabber', 'checked': true }), ' Jabber']), E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-matrix', 'checked': true }), ' Matrix']), E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-email', 'checked': true }), ' Email']), - E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-gitea', 'checked': true }), ' Gitea']) + E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-gitea', 'checked': true }), ' Gitea']), + E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-jellyfin', 'checked': true }), ' Jellyfin']) ]) ]) ]), @@ -138,6 +139,7 @@ return view.extend({ if (document.getElementById('svc-matrix').checked) services.push('matrix'); if (document.getElementById('svc-email').checked) services.push('email'); if (document.getElementById('svc-gitea').checked) services.push('gitea'); + if (document.getElementById('svc-jellyfin').checked) services.push('jellyfin'); if (!username) { ui.addNotification(null, E('p', {}, _('Username required')), 'error'); @@ -266,7 +268,8 @@ return view.extend({ this.renderServiceBadge('Matrix', services.matrix), this.renderServiceBadge('Jabber', services.jabber), this.renderServiceBadge('Email', services.email), - this.renderServiceBadge('Gitea', services.gitea) + this.renderServiceBadge('Gitea', services.gitea), + this.renderServiceBadge('Jellyfin', services.jellyfin) ]), E('p', { 'style': 'color:#666;' }, _('Domain: %s | Users: %d').format(status.domain, status.user_count)) ])); diff --git a/package/secubox/luci-app-secubox-users/root/usr/libexec/rpcd/luci.secubox-users b/package/secubox/luci-app-secubox-users/root/usr/libexec/rpcd/luci.secubox-users index 5a64359b..e600a54d 100644 --- a/package/secubox/luci-app-secubox-users/root/usr/libexec/rpcd/luci.secubox-users +++ b/package/secubox/luci-app-secubox-users/root/usr/libexec/rpcd/luci.secubox-users @@ -17,6 +17,7 @@ check_service() { jabber) [ -x /usr/sbin/jabberctl ] && lxc-info -n jabber 2>/dev/null | grep -q "RUNNING" && echo "1" || echo "0" ;; email) [ -x /usr/sbin/mailserverctl ] && lxc-info -n mailserver 2>/dev/null | grep -q "RUNNING" && echo "1" || echo "0" ;; gitea) [ -x /usr/sbin/giteactl ] && lxc-info -n gitea 2>/dev/null | grep -q "RUNNING" && echo "1" || echo "0" ;; + jellyfin) [ -x /usr/sbin/jellyfinctl ] && lxc-info -n jellyfin 2>/dev/null | grep -q "RUNNING" && echo "1" || echo "0" ;; *) echo "0" ;; esac } @@ -32,6 +33,7 @@ get_status() { local jb_running=$(check_service jabber) local em_running=$(check_service email) local gt_running=$(check_service gitea) + local jf_running=$(check_service jellyfin) cat < 127.0.0.1:$port" + # Get the host's LAN IP (mitmproxy runs in container, can't reach 127.0.0.1 on host) + local host_ip + host_ip=$(uci -q get network.lan.ipaddr || echo "192.168.255.1") + + log_info "[WAF] Adding route: $domain -> $host_ip:$port" # Direct JSON update - most reliable method if [ -f "$routes_file" ] && command -v python3 >/dev/null 2>&1; then @@ -877,7 +881,7 @@ import sys try: with open('$routes_file', 'r') as f: routes = json.load(f) - routes['$domain'] = ['127.0.0.1', $port] + routes['$domain'] = ['$host_ip', $port] with open('$routes_file', 'w') as f: json.dump(routes, f, indent=2) print('Route added successfully') @@ -892,7 +896,7 @@ except Exception as e: # Fallback: Use centralized secubox-route if available if command -v secubox-route >/dev/null 2>&1; then - if secubox-route add "$domain" "127.0.0.1" "$port" "metablogizer" 2>&1; then + if secubox-route add "$domain" "$host_ip" "$port" "metablogizer" 2>&1; then log_info "[WAF] Route registered via secubox-route" return 0 fi diff --git a/package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxyctl b/package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxyctl index 2498b547..cda2606a 100755 --- a/package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxyctl +++ b/package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxyctl @@ -1482,6 +1482,10 @@ sync_metablogizer_routes() { log_info "Scanning MetaBlogizer sites..." + # Get host IP (mitmproxy runs in container, can't reach 127.0.0.1 on host) + local host_ip + host_ip=$(uci -q get network.lan.ipaddr || echo "192.168.255.1") + # Get all metablogizer sites local sites_file="/tmp/mb_sites.tmp" uci show metablogizer 2>/dev/null | grep "=site$" | cut -d'=' -f1 | cut -d'.' -f2 > "$sites_file" @@ -1503,9 +1507,9 @@ sync_metablogizer_routes() { fi echo "$domain" >> "$added_file" - # Output JSON fragment - echo "\"$domain\": [\"127.0.0.1\", $port]" - log_info " $domain -> 127.0.0.1:$port (metablogizer)" + # Output JSON fragment (use host IP, not 127.0.0.1) + echo "\"$domain\": [\"$host_ip\", $port]" + log_info " $domain -> $host_ip:$port (metablogizer)" done < "$sites_file" rm -f "$sites_file" diff --git a/package/secubox/secubox-app-nextcloud/files/usr/sbin/nextcloudctl b/package/secubox/secubox-app-nextcloud/files/usr/sbin/nextcloudctl index bfac0dd8..f364ba94 100644 --- a/package/secubox/secubox-app-nextcloud/files/usr/sbin/nextcloudctl +++ b/package/secubox/secubox-app-nextcloud/files/usr/sbin/nextcloudctl @@ -997,9 +997,15 @@ cmd_ssl_enable() { # Add route to mitmproxy for WAF inspection # This ensures traffic is inspected before reaching Nextcloud + # NOTE: Nextcloud uses host network (lxc.net.0.type=none) so it listens on 192.168.255.1 + # mitmproxy runs in its own container, so it can't reach 127.0.0.1 on the host local routes_file="/srv/mitmproxy/haproxy-routes.json" local routes_file_in="/srv/mitmproxy-in/haproxy-routes.json" + # Get the host's LAN IP (where Nextcloud is accessible from mitmproxy container) + local host_ip + host_ip=$(uci -q get network.lan.ipaddr || echo "192.168.255.1") + if [ -f "$routes_file" ]; then log_info "Adding mitmproxy route for WAF inspection..." # Use jsonfilter to check if route exists, then add if not @@ -1007,7 +1013,7 @@ cmd_ssl_enable() { existing=$(jsonfilter -i "$routes_file" -e "@[\"$domain\"]" 2>/dev/null || echo "") if [ -z "$existing" ]; then - # Add route: domain -> [127.0.0.1, port] + # Add route: domain -> [host_ip, port] local tmpfile="/tmp/routes-$$.json" # Read existing routes and add new one if command -v python3 >/dev/null 2>&1; then @@ -1015,17 +1021,17 @@ cmd_ssl_enable() { import json with open('$routes_file', 'r') as f: routes = json.load(f) -routes['$domain'] = ['127.0.0.1', $http_port] +routes['$domain'] = ['$host_ip', $http_port] with open('$tmpfile', 'w') as f: json.dump(routes, f, indent=2) " mv "$tmpfile" "$routes_file" # Also update the in-container copy [ -f "$routes_file_in" ] && cp "$routes_file" "$routes_file_in" - log_info "Added WAF route: $domain -> 127.0.0.1:$http_port" + log_info "Added WAF route: $domain -> $host_ip:$http_port" else log_info "Python3 not available, manual route config needed" - log_info "Add to $routes_file: \"$domain\": [\"127.0.0.1\", $http_port]" + log_info "Add to $routes_file: \"$domain\": [\"$host_ip\", $http_port]" fi else log_info "WAF route already exists for $domain" @@ -1033,7 +1039,7 @@ with open('$tmpfile', 'w') as f: else log_info "mitmproxy routes file not found, creating..." mkdir -p "$(dirname "$routes_file")" - echo "{\"$domain\": [\"127.0.0.1\", $http_port]}" > "$routes_file" + echo "{\"$domain\": [\"$host_ip\", $http_port]}" > "$routes_file" [ -d "$(dirname "$routes_file_in")" ] && cp "$routes_file" "$routes_file_in" fi diff --git a/package/secubox/secubox-core-users/files/usr/sbin/secubox-users b/package/secubox/secubox-core-users/files/usr/sbin/secubox-users index 7ea10e11..b89115c0 100644 --- a/package/secubox/secubox-core-users/files/usr/sbin/secubox-users +++ b/package/secubox/secubox-core-users/files/usr/sbin/secubox-users @@ -51,6 +51,8 @@ check_service() { matrix) [ -x /usr/sbin/matrixctl ] && lxc-info -n matrix 2>/dev/null | grep -q "RUNNING" ;; jabber) [ -x /usr/sbin/jabberctl ] && lxc-info -n jabber 2>/dev/null | grep -q "RUNNING" ;; email) [ -x /usr/sbin/mailserverctl ] && lxc-info -n mailserver 2>/dev/null | grep -q "RUNNING" ;; + gitea) [ -x /usr/sbin/giteactl ] && lxc-info -n gitea 2>/dev/null | grep -q "RUNNING" ;; + jellyfin) [ -x /usr/sbin/jellyfinctl ] && lxc-info -n jellyfin 2>/dev/null | grep -q "RUNNING" ;; *) return 1 ;; esac } @@ -86,6 +88,23 @@ create_on_service() { email) mailserverctl add-user "$email" "$password" 2>/dev/null ;; + gitea) + giteactl admin create-user --username "$username" --password "$password" --email "$email" 2>/dev/null + ;; + jellyfin) + # Jellyfin user creation via API + local jf_ip=$(uci -q get jellyfin.main.ip_address || echo "192.168.255.31") + local jf_port=$(uci -q get jellyfin.main.port || echo "8096") + local jf_api_key=$(uci -q get jellyfin.main.api_key) + if [ -n "$jf_api_key" ]; then + curl -s -X POST "http://${jf_ip}:${jf_port}/Users/New" \ + -H "X-Emby-Token: ${jf_api_key}" \ + -H "Content-Type: application/json" \ + -d "{\"Name\":\"${username}\",\"Password\":\"${password}\"}" 2>/dev/null + else + log_warn "Jellyfin: No API key configured" + fi + ;; esac } @@ -118,6 +137,40 @@ update_password_on_service() { sed -i "/^${email}:/d" /srv/lxc/mailserver/rootfs/etc/dovecot/users 2>/dev/null mailserverctl add-user "$email" "$password" 2>/dev/null ;; + gitea) + # Gitea password update via API + local gitea_url=$(uci -q get gitea.main.url 2>/dev/null || echo "http://192.168.255.1:3001") + local gitea_token=$(uci -q get gitea.main.token 2>/dev/null) + if [ -n "$gitea_token" ]; then + curl -s -X PATCH \ + -H "Authorization: token $gitea_token" \ + -H "Content-Type: application/json" \ + -d "{\"login_name\":\"$username\",\"password\":\"$password\"}" \ + "${gitea_url}/api/v1/admin/users/${username}" 2>/dev/null + else + log_warn "Gitea: No API token configured" + fi + ;; + jellyfin) + # Jellyfin password update via API + local jf_ip=$(uci -q get jellyfin.main.ip_address || echo "192.168.255.31") + local jf_port=$(uci -q get jellyfin.main.port || echo "8096") + local jf_api_key=$(uci -q get jellyfin.main.api_key) + if [ -n "$jf_api_key" ]; then + # Get user ID first + local user_id=$(curl -s "http://${jf_ip}:${jf_port}/Users" \ + -H "X-Emby-Token: ${jf_api_key}" 2>/dev/null | \ + jsonfilter -e "@[?(@.Name=='${username}')].Id" 2>/dev/null) + if [ -n "$user_id" ]; then + curl -s -X POST "http://${jf_ip}:${jf_port}/Users/${user_id}/Password" \ + -H "X-Emby-Token: ${jf_api_key}" \ + -H "Content-Type: application/json" \ + -d "{\"NewPw\":\"${password}\"}" 2>/dev/null + fi + else + log_warn "Jellyfin: No API key configured" + fi + ;; esac } @@ -147,6 +200,35 @@ delete_from_service() { sed -i "/^${email}:/d" /srv/lxc/mailserver/rootfs/etc/dovecot/users 2>/dev/null sed -i "/${email}/d" /srv/lxc/mailserver/rootfs/etc/postfix/vmailbox 2>/dev/null ;; + gitea) + # Gitea user deletion via API + local gitea_url=$(uci -q get gitea.main.url 2>/dev/null || echo "http://192.168.255.1:3001") + local gitea_token=$(uci -q get gitea.main.token 2>/dev/null) + if [ -n "$gitea_token" ]; then + curl -s -X DELETE \ + -H "Authorization: token $gitea_token" \ + "${gitea_url}/api/v1/admin/users/${username}" 2>/dev/null + else + log_warn "Gitea: No API token configured - manual deletion required" + fi + ;; + jellyfin) + # Jellyfin user deletion via API + local jf_ip=$(uci -q get jellyfin.main.ip_address || echo "192.168.255.31") + local jf_port=$(uci -q get jellyfin.main.port || echo "8096") + local jf_api_key=$(uci -q get jellyfin.main.api_key) + if [ -n "$jf_api_key" ]; then + local user_id=$(curl -s "http://${jf_ip}:${jf_port}/Users" \ + -H "X-Emby-Token: ${jf_api_key}" 2>/dev/null | \ + jsonfilter -e "@[?(@.Name=='${username}')].Id" 2>/dev/null) + if [ -n "$user_id" ]; then + curl -s -X DELETE "http://${jf_ip}:${jf_port}/Users/${user_id}" \ + -H "X-Emby-Token: ${jf_api_key}" 2>/dev/null + fi + else + log_warn "Jellyfin: No API key configured - manual deletion required" + fi + ;; esac } @@ -172,7 +254,7 @@ cmd_add() { # Default to all services if not specified if [ -z "$services" ]; then - services=$(uci_get main.default_services || echo "nextcloud peertube jabber matrix email") + services=$(uci_get main.default_services || echo "nextcloud peertube jabber matrix email gitea jellyfin") else services=$(echo "$services" | tr ',' ' ') fi @@ -440,7 +522,7 @@ cmd_status() { echo "" echo "Available Services:" - for svc in nextcloud peertube jabber matrix email; do + for svc in nextcloud peertube jabber matrix email gitea jellyfin; do if check_service "$svc"; then echo -e " ${GREEN}●${NC} $svc" else @@ -470,7 +552,7 @@ Commands: status Show status and available services Services (comma-separated): - nextcloud, peertube, jabber, matrix, email + nextcloud, peertube, jabber, matrix, email, gitea, jellyfin Examples: secubox-users add alice # All services, generated password