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 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-03-04 10:16:07 +01:00
parent 518891d538
commit 2bb40d9419
6 changed files with 119 additions and 17 deletions

View File

@ -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))
]));

View File

@ -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 <<EOFJ
{
@ -44,7 +46,8 @@ get_status() {
"matrix": $mx_running,
"jabber": $jb_running,
"email": $em_running,
"gitea": $gt_running
"gitea": $gt_running,
"jellyfin": $jf_running
}
}
EOFJ

View File

@ -867,7 +867,11 @@ _emancipate_mitmproxy() {
local port=$(uci_get site_${name}.port)
local routes_file="/srv/mitmproxy-in/haproxy-routes.json"
log_info "[WAF] Adding route: $domain -> 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

View File

@ -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"

View File

@ -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

View File

@ -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