fix(multi): Exposure fixes, MagicMirror2 port, Tor Shield health card

Exposure Manager:
- Fix RPCD subshell issues in status and ssl_list methods
- Fix JS views to handle both array and object API responses

MagicMirror2:
- Change default port from 8082 to 8085 (avoid CyberFeed conflict)
- Update mm2ctl, RPCD, settings.js, dashboard.js, config

Tor Shield:
- Add restart method to RPCD and API
- Add health status minicard (Service, Bootstrap, DNS, Kill Switch)

Portal:
- Add 'active-ports' section for detected services
- Separate portal apps (Services) from detected ports (Active Ports)

Service Detection:
- Prioritize port-based identification over process name

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-25 17:22:52 +01:00
parent 7566014096
commit a1bad31807
17 changed files with 212 additions and 78 deletions

View File

@ -14,8 +14,10 @@ return view.extend({
render: function(data) { render: function(data) {
var status = data[0] || {}; var status = data[0] || {};
var conflicts = data[1] || []; var conflictsResult = data[1] || {};
// Handle both direct array and wrapped object responses
var conflicts = Array.isArray(conflictsResult) ? conflictsResult : (conflictsResult.conflicts || []);
var services = status.services || {}; var services = status.services || {};
var tor = status.tor || {}; var tor = status.tor || {};
var ssl = status.ssl || {}; var ssl = status.ssl || {};

View File

@ -13,8 +13,10 @@ return view.extend({
}, },
render: function(data) { render: function(data) {
var services = data[0] || []; var scanResult = data[0] || {};
var config = data[1] || []; var configResult = data[1] || {};
var services = Array.isArray(scanResult) ? scanResult : (scanResult.services || []);
var config = Array.isArray(configResult) ? configResult : (configResult.known_services || []);
var self = this; var self = this;
// Inject CSS // Inject CSS

View File

@ -13,8 +13,10 @@ return view.extend({
}, },
render: function(data) { render: function(data) {
var sslBackends = data[0] || []; var sslResult = data[0] || {};
var allServices = data[1] || []; var scanResult = data[1] || {};
var sslBackends = Array.isArray(sslResult) ? sslResult : (sslResult.backends || []);
var allServices = Array.isArray(scanResult) ? scanResult : (scanResult.services || []);
var self = this; var self = this;
// Inject CSS // Inject CSS

View File

@ -13,8 +13,10 @@ return view.extend({
}, },
render: function(data) { render: function(data) {
var torServices = data[0] || []; var torResult = data[0] || {};
var allServices = data[1] || []; var scanResult = data[1] || {};
var torServices = Array.isArray(torResult) ? torResult : (torResult.services || []);
var allServices = Array.isArray(scanResult) ? scanResult : (scanResult.services || []);
var self = this; var self = this;
// Inject CSS // Inject CSS

View File

@ -137,23 +137,32 @@ case "$1" in
json_close_array json_close_array
json_close_object json_close_object
# HAProxy SSL backends # HAProxy SSL backends - use temp file to avoid subshell
HAPROXY_CONFIG="/srv/lxc/haproxy/rootfs/etc/haproxy/haproxy.cfg" HAPROXY_CONFIG="/srv/lxc/haproxy/rootfs/etc/haproxy/haproxy.cfg"
ssl_count=0 ssl_count=0
[ -f "$HAPROXY_CONFIG" ] && ssl_count=$(grep -c "^backend.*_backend$" "$HAPROXY_CONFIG" 2>/dev/null || echo 0) [ -f "$HAPROXY_CONFIG" ] && ssl_count=$(grep -c "^backend.*_backend$" "$HAPROXY_CONFIG" 2>/dev/null || echo 0)
TMP_SSL="/tmp/exposure_ssl_$$"
if [ -f "$HAPROXY_CONFIG" ]; then
grep -E "^backend .+_backend$" "$HAPROXY_CONFIG" 2>/dev/null | while read line; do
backend=$(echo "$line" | awk '{print $2}' | sed 's/_backend$//')
domain=$(grep "acl host_${backend} " "$HAPROXY_CONFIG" 2>/dev/null | awk '{print $NF}')
echo "$backend ${domain:-N/A}"
done > "$TMP_SSL"
fi
json_add_object "ssl" json_add_object "ssl"
json_add_int "count" "$ssl_count" json_add_int "count" "$ssl_count"
json_add_array "backends" json_add_array "backends"
if [ -f "$HAPROXY_CONFIG" ]; then if [ -f "$TMP_SSL" ]; then
grep -E "^backend .+_backend$" "$HAPROXY_CONFIG" 2>/dev/null | while read line; do while read backend domain; do
backend=$(echo "$line" | awk '{print $2}' | sed 's/_backend$//') [ -z "$backend" ] && continue
domain=$(grep "acl host_${backend} " "$HAPROXY_CONFIG" 2>/dev/null | awk '{print $NF}')
json_add_object "" json_add_object ""
json_add_string "service" "$backend" json_add_string "service" "$backend"
json_add_string "domain" "${domain:-N/A}" json_add_string "domain" "$domain"
json_close_object json_close_object
done done < "$TMP_SSL"
rm -f "$TMP_SSL"
fi fi
json_close_array json_close_array
json_close_object json_close_object
@ -195,23 +204,32 @@ case "$1" in
ssl_list) ssl_list)
HAPROXY_CONFIG="/srv/lxc/haproxy/rootfs/etc/haproxy/haproxy.cfg" HAPROXY_CONFIG="/srv/lxc/haproxy/rootfs/etc/haproxy/haproxy.cfg"
TMP_SSLLIST="/tmp/exposure_ssllist_$$"
json_init # Extract backend info to temp file to avoid subshell issues
json_add_array "backends"
if [ -f "$HAPROXY_CONFIG" ]; then if [ -f "$HAPROXY_CONFIG" ]; then
grep -E "^backend .+_backend$" "$HAPROXY_CONFIG" 2>/dev/null | while read line; do grep -E "^backend .+_backend$" "$HAPROXY_CONFIG" 2>/dev/null | while read line; do
backend=$(echo "$line" | awk '{print $2}') backend=$(echo "$line" | awk '{print $2}')
service=$(echo "$backend" | sed 's/_backend$//') service=$(echo "$backend" | sed 's/_backend$//')
domain=$(grep "acl host_${service} " "$HAPROXY_CONFIG" 2>/dev/null | awk '{print $NF}') domain=$(grep "acl host_${service} " "$HAPROXY_CONFIG" 2>/dev/null | awk '{print $NF}')
server=$(grep -A5 "backend $backend" "$HAPROXY_CONFIG" 2>/dev/null | grep "server " | awk '{print $3}') server=$(grep -A5 "backend $backend" "$HAPROXY_CONFIG" 2>/dev/null | grep "server " | awk '{print $3}')
echo "$service|${domain:-N/A}|${server:-N/A}"
done > "$TMP_SSLLIST"
fi
json_init
json_add_array "backends"
if [ -f "$TMP_SSLLIST" ]; then
while IFS='|' read service domain server; do
[ -z "$service" ] && continue
json_add_object "" json_add_object ""
json_add_string "service" "$service" json_add_string "service" "$service"
json_add_string "domain" "${domain:-N/A}" json_add_string "domain" "$domain"
json_add_string "backend" "${server:-N/A}" json_add_string "backend" "$server"
json_close_object json_close_object
done done < "$TMP_SSLLIST"
rm -f "$TMP_SSLLIST"
fi fi
json_close_array json_close_array

View File

@ -126,7 +126,7 @@ return view.extend({
]), ]),
E('div', { 'class': 'mm2-card' }, [ E('div', { 'class': 'mm2-card' }, [
E('div', { 'class': 'mm2-stat' }, [ E('div', { 'class': 'mm2-stat' }, [
E('div', { 'class': 'mm2-stat-value' }, ':' + (config.port || 8082)), E('div', { 'class': 'mm2-stat-value' }, ':' + (config.port || 8085)),
E('div', { 'class': 'mm2-stat-label' }, _('Web Port')) E('div', { 'class': 'mm2-stat-label' }, _('Web Port'))
]) ])
]), ]),

View File

@ -67,7 +67,7 @@ return view.extend({
o = s.option(form.Value, 'port', _('Web Port')); o = s.option(form.Value, 'port', _('Web Port'));
o.datatype = 'port'; o.datatype = 'port';
o.default = '8082'; o.default = '8085';
o.rmempty = false; o.rmempty = false;
o = s.option(form.Value, 'address', _('Listen Address')); o = s.option(form.Value, 'address', _('Listen Address'));

View File

@ -26,7 +26,7 @@ get_status() {
fi fi
local enabled=$(uci -q get magicmirror2.main.enabled || echo "0") local enabled=$(uci -q get magicmirror2.main.enabled || echo "0")
local port=$(uci -q get magicmirror2.main.port || echo "8082") local port=$(uci -q get magicmirror2.main.port || echo "8085")
local router_ip=$(uci -q get network.lan.ipaddr || echo "192.168.1.1") local router_ip=$(uci -q get network.lan.ipaddr || echo "192.168.1.1")
[ "$running" = "1" ] && web_url="http://${router_ip}:${port}" [ "$running" = "1" ] && web_url="http://${router_ip}:${port}"
@ -54,7 +54,7 @@ EOF
# Get main configuration # Get main configuration
get_config() { get_config() {
local enabled=$(uci -q get magicmirror2.main.enabled || echo "0") local enabled=$(uci -q get magicmirror2.main.enabled || echo "0")
local port=$(uci -q get magicmirror2.main.port || echo "8082") local port=$(uci -q get magicmirror2.main.port || echo "8085")
local address=$(uci -q get magicmirror2.main.address || echo "0.0.0.0") local address=$(uci -q get magicmirror2.main.address || echo "0.0.0.0")
local data_path=$(uci -q get magicmirror2.main.data_path || echo "/srv/magicmirror2") local data_path=$(uci -q get magicmirror2.main.data_path || echo "/srv/magicmirror2")
local memory_limit=$(uci -q get magicmirror2.main.memory_limit || echo "512M") local memory_limit=$(uci -q get magicmirror2.main.memory_limit || echo "512M")
@ -327,7 +327,7 @@ set_config() {
# Get web URL for iframe # Get web URL for iframe
get_web_url() { get_web_url() {
local router_ip=$(uci -q get network.lan.ipaddr || echo "192.168.1.1") local router_ip=$(uci -q get network.lan.ipaddr || echo "192.168.1.1")
local port=$(uci -q get magicmirror2.main.port || echo "8082") local port=$(uci -q get magicmirror2.main.port || echo "8085")
cat <<EOF cat <<EOF
{ {

View File

@ -440,6 +440,13 @@ return baseclass.extend({
icon: '\ud83d\udce6', icon: '\ud83d\udce6',
path: 'admin/secubox/services', path: 'admin/secubox/services',
order: 8 order: 8
},
'active-ports': {
id: 'active-ports',
name: 'Active Ports',
icon: '\ud83d\udd0c',
path: 'admin/secubox/services',
order: 9
} }
}, },

View File

@ -156,7 +156,8 @@ return view.extend({
this.renderNetworkSection(), this.renderNetworkSection(),
this.renderMonitoringSection(), this.renderMonitoringSection(),
this.renderSystemSection(), this.renderSystemSection(),
this.renderServicesSection() this.renderServicesAppsSection(),
this.renderActivePortsSection()
]) ])
]); ]);
@ -171,7 +172,7 @@ return view.extend({
var sections = portal.getSections(); var sections = portal.getSections();
// Sections that link to other pages vs tabs within portal // Sections that link to other pages vs tabs within portal
var linkSections = ['portal', 'hub', 'admin']; var linkSections = ['portal', 'hub', 'admin'];
var tabSections = ['security', 'network', 'monitoring', 'system', 'services']; var tabSections = ['security', 'network', 'monitoring', 'system', 'services', 'active-ports'];
return E('div', { 'class': 'sb-portal-header' }, [ return E('div', { 'class': 'sb-portal-header' }, [
// Brand // Brand
@ -443,7 +444,13 @@ return view.extend({
'System administration and configuration tools', apps); 'System administration and configuration tools', apps);
}, },
renderServicesSection: function() { renderServicesAppsSection: function() {
var apps = portal.getInstalledAppsBySection('services', this.installedApps);
return this.renderAppSection('services', 'Services',
'Application services running on your network', apps);
},
renderActivePortsSection: function() {
var self = this; var self = this;
var services = this.detectedServices || []; var services = this.detectedServices || [];
@ -514,9 +521,9 @@ return view.extend({
}); });
if (serviceCards.length === 0) { if (serviceCards.length === 0) {
return E('div', { 'class': 'sb-portal-section', 'data-section': 'services' }, [ return E('div', { 'class': 'sb-portal-section', 'data-section': 'active-ports' }, [
E('div', { 'class': 'sb-section-header' }, [ E('div', { 'class': 'sb-section-header' }, [
E('h2', { 'class': 'sb-section-title' }, '🔌 Active Services'), E('h2', { 'class': 'sb-section-title' }, '🔌 Active Ports'),
E('p', { 'class': 'sb-section-subtitle' }, 'Detected services listening on network ports') E('p', { 'class': 'sb-section-subtitle' }, 'Detected services listening on network ports')
]), ]),
E('div', { 'class': 'sb-section-empty' }, [ E('div', { 'class': 'sb-section-empty' }, [
@ -527,9 +534,9 @@ return view.extend({
]); ]);
} }
return E('div', { 'class': 'sb-portal-section', 'data-section': 'services' }, [ return E('div', { 'class': 'sb-portal-section', 'data-section': 'active-ports' }, [
E('div', { 'class': 'sb-section-header' }, [ E('div', { 'class': 'sb-section-header' }, [
E('h2', { 'class': 'sb-section-title' }, '🔌 Active Services'), E('h2', { 'class': 'sb-section-title' }, '🔌 Active Ports'),
E('p', { 'class': 'sb-section-subtitle' }, 'Detected services listening on network ports') E('p', { 'class': 'sb-section-subtitle' }, 'Detected services listening on network ports')
]), ]),
E('div', { 'class': 'sb-app-grid' }, serviceCards) E('div', { 'class': 'sb-app-grid' }, serviceCards)

View File

@ -27,6 +27,12 @@ var callDisable = rpc.declare({
expect: { success: false } expect: { success: false }
}); });
var callRestart = rpc.declare({
object: 'luci.tor-shield',
method: 'restart',
expect: { success: false }
});
var callCircuits = rpc.declare({ var callCircuits = rpc.declare({
object: 'luci.tor-shield', object: 'luci.tor-shield',
method: 'circuits', method: 'circuits',
@ -161,6 +167,7 @@ return baseclass.extend({
getStatus: callStatus, getStatus: callStatus,
enable: callEnable, enable: callEnable,
disable: callDisable, disable: callDisable,
restart: callRestart,
getCircuits: callCircuits, getCircuits: callCircuits,
newIdentity: callNewIdentity, newIdentity: callNewIdentity,
checkLeaks: callCheckLeaks, checkLeaks: callCheckLeaks,

View File

@ -87,6 +87,27 @@ return view.extend({
}); });
}, },
// Handle restart
handleRestart: function() {
var self = this;
ui.showModal(_('Restart Tor Shield'), [
E('p', { 'class': 'spinning' }, _('Restarting Tor Shield service...'))
]);
api.restart().then(function(result) {
ui.hideModal();
if (result.success) {
ui.addNotification(null, E('p', _('Tor Shield is restarting. Please wait for bootstrap to complete.')), 'info');
} else {
ui.addNotification(null, E('p', result.error || _('Failed to restart')), 'error');
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', _('Error: %s').format(err.message || err)), 'error');
});
},
// Handle leak test // Handle leak test
handleLeakTest: function() { handleLeakTest: function() {
var self = this; var self = this;
@ -373,6 +394,54 @@ return view.extend({
]) ])
]), ]),
// Health Status Minicard
E('div', { 'class': 'tor-health-card', 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; margin-bottom: 20px;' }, [
E('div', { 'class': 'tor-health-item', 'style': 'display: flex; align-items: center; gap: 12px; padding: 16px; background: var(--tor-bg-card, #1a1a24); border-radius: 12px; border: 1px solid rgba(255,255,255,0.05);' }, [
E('div', {
'class': 'tor-health-indicator',
'style': 'width: 12px; height: 12px; border-radius: 50%; background: ' + (isProtected ? '#10b981' : isConnecting ? '#f59e0b' : '#6b7280') + '; box-shadow: 0 0 8px ' + (isProtected ? '#10b981' : isConnecting ? '#f59e0b' : 'transparent') + ';'
}),
E('div', {}, [
E('div', { 'style': 'font-size: 14px; font-weight: 600; color: var(--tor-text, #fff);' }, _('Service')),
E('div', { 'style': 'font-size: 12px; color: var(--tor-text-muted, #a0a0b0);' },
status.running ? _('Running') : _('Stopped'))
])
]),
E('div', { 'class': 'tor-health-item', 'style': 'display: flex; align-items: center; gap: 12px; padding: 16px; background: var(--tor-bg-card, #1a1a24); border-radius: 12px; border: 1px solid rgba(255,255,255,0.05);' }, [
E('div', {
'class': 'tor-health-indicator',
'style': 'width: 12px; height: 12px; border-radius: 50%; background: ' + (status.bootstrap >= 100 ? '#10b981' : status.bootstrap > 0 ? '#f59e0b' : '#6b7280') + '; box-shadow: 0 0 8px ' + (status.bootstrap >= 100 ? '#10b981' : status.bootstrap > 0 ? '#f59e0b' : 'transparent') + ';'
}),
E('div', {}, [
E('div', { 'style': 'font-size: 14px; font-weight: 600; color: var(--tor-text, #fff);' }, _('Bootstrap')),
E('div', { 'style': 'font-size: 12px; color: var(--tor-text-muted, #a0a0b0);' },
status.bootstrap >= 100 ? _('Complete') : status.bootstrap + '%')
])
]),
E('div', { 'class': 'tor-health-item', 'style': 'display: flex; align-items: center; gap: 12px; padding: 16px; background: var(--tor-bg-card, #1a1a24); border-radius: 12px; border: 1px solid rgba(255,255,255,0.05);' }, [
E('div', {
'class': 'tor-health-indicator',
'style': 'width: 12px; height: 12px; border-radius: 50%; background: ' + (status.dns_over_tor ? '#10b981' : '#f59e0b') + '; box-shadow: 0 0 8px ' + (status.dns_over_tor ? '#10b981' : '#f59e0b') + ';'
}),
E('div', {}, [
E('div', { 'style': 'font-size: 14px; font-weight: 600; color: var(--tor-text, #fff);' }, _('DNS')),
E('div', { 'style': 'font-size: 12px; color: var(--tor-text-muted, #a0a0b0);' },
status.dns_over_tor ? _('Protected') : _('Exposed'))
])
]),
E('div', { 'class': 'tor-health-item', 'style': 'display: flex; align-items: center; gap: 12px; padding: 16px; background: var(--tor-bg-card, #1a1a24); border-radius: 12px; border: 1px solid rgba(255,255,255,0.05);' }, [
E('div', {
'class': 'tor-health-indicator',
'style': 'width: 12px; height: 12px; border-radius: 50%; background: ' + (status.kill_switch ? '#10b981' : '#6b7280') + '; box-shadow: 0 0 8px ' + (status.kill_switch ? '#10b981' : 'transparent') + ';'
}),
E('div', {}, [
E('div', { 'style': 'font-size: 14px; font-weight: 600; color: var(--tor-text, #fff);' }, _('Kill Switch')),
E('div', { 'style': 'font-size: 12px; color: var(--tor-text-muted, #a0a0b0);' },
status.kill_switch ? _('Active') : _('Disabled'))
])
])
]),
// Actions Card // Actions Card
E('div', { 'class': 'tor-card' }, [ E('div', { 'class': 'tor-card' }, [
E('div', { 'class': 'tor-card-header' }, [ E('div', { 'class': 'tor-card-header' }, [
@ -393,6 +462,11 @@ return view.extend({
'click': L.bind(this.handleLeakTest, this), 'click': L.bind(this.handleLeakTest, this),
'disabled': !isActive 'disabled': !isActive
}, ['\uD83D\uDD0D ', _('Leak Test')]), }, ['\uD83D\uDD0D ', _('Leak Test')]),
E('button', {
'class': 'tor-btn tor-btn-warning',
'click': L.bind(this.handleRestart, this),
'disabled': !status.enabled
}, ['\u21BB ', _('Restart')]),
E('a', { E('a', {
'class': 'tor-btn', 'class': 'tor-btn',
'href': L.url('admin', 'services', 'tor-shield', 'circuits') 'href': L.url('admin', 'services', 'tor-shield', 'circuits')

View File

@ -707,9 +707,21 @@ save_settings() {
} }
# Main dispatcher # Main dispatcher
# Restart Tor Shield service
do_restart() {
json_init
/etc/init.d/tor-shield restart >/dev/null 2>&1 &
json_add_boolean "success" 1
json_add_string "message" "Tor Shield restarting"
json_dump
}
case "$1" in case "$1" in
list) list)
echo '{"status":{},"enable":{"preset":"str"},"disable":{},"circuits":{},"new_identity":{},"check_leaks":{},"hidden_services":{},"add_hidden_service":{"name":"str","local_port":"int","virtual_port":"int"},"remove_hidden_service":{"name":"str"},"exit_ip":{},"bandwidth":{},"presets":{},"bridges":{},"set_bridges":{"enabled":"bool","type":"str"},"settings":{},"save_settings":{"mode":"str","dns_over_tor":"bool","kill_switch":"bool","socks_port":"int","trans_port":"int","dns_port":"int","exit_nodes":"str","exclude_exit_nodes":"str","strict_nodes":"bool"}}' echo '{"status":{},"enable":{"preset":"str"},"disable":{},"restart":{},"circuits":{},"new_identity":{},"check_leaks":{},"hidden_services":{},"add_hidden_service":{"name":"str","local_port":"int","virtual_port":"int"},"remove_hidden_service":{"name":"str"},"exit_ip":{},"bandwidth":{},"presets":{},"bridges":{},"set_bridges":{"enabled":"bool","type":"str"},"settings":{},"save_settings":{"mode":"str","dns_over_tor":"bool","kill_switch":"bool","socks_port":"int","trans_port":"int","dns_port":"int","exit_nodes":"str","exclude_exit_nodes":"str","strict_nodes":"bool"}}'
;; ;;
call) call)
case "$2" in case "$2" in
@ -722,6 +734,9 @@ case "$1" in
disable) disable)
do_disable do_disable
;; ;;
restart)
do_restart
;;
circuits) circuits)
get_circuits get_circuits
;; ;;

View File

@ -60,7 +60,7 @@ define Package/secubox-app-magicmirror2/postinst
echo " mm2ctl install" echo " mm2ctl install"
echo " /etc/init.d/magicmirror2 start" echo " /etc/init.d/magicmirror2 start"
echo "" echo ""
echo "Web interface: http://<router-ip>:8082" echo "Web interface: http://<router-ip>:8085"
echo "" echo ""
echo "To manage modules:" echo "To manage modules:"
echo " mm2ctl module list" echo " mm2ctl module list"

View File

@ -2,7 +2,7 @@
config magicmirror2 'main' config magicmirror2 'main'
option enabled '0' option enabled '0'
option port '8082' option port '8085'
option address '0.0.0.0' option address '0.0.0.0'
option data_path '/srv/magicmirror2' option data_path '/srv/magicmirror2'
option memory_limit '512M' option memory_limit '512M'

View File

@ -51,7 +51,7 @@ Examples:
mm2ctl module list mm2ctl module list
mm2ctl config mm2ctl config
Web Interface: http://<router-ip>:8082 Web Interface: http://<router-ip>:8085
EOF EOF
} }
@ -66,7 +66,7 @@ uci_set() { uci set ${CONFIG}.$1="$2" && uci commit ${CONFIG}; }
# Load configuration with defaults # Load configuration with defaults
load_config() { load_config() {
port="$(uci_get main.port || echo 8082)" port="$(uci_get main.port || echo 8085)"
address="$(uci_get main.address || echo 0.0.0.0)" address="$(uci_get main.address || echo 0.0.0.0)"
data_path="$(uci_get main.data_path || echo /srv/magicmirror2)" data_path="$(uci_get main.data_path || echo /srv/magicmirror2)"
memory_limit="$(uci_get main.memory_limit || echo 512M)" memory_limit="$(uci_get main.memory_limit || echo 512M)"
@ -255,7 +255,7 @@ lxc_create_docker_rootfs() {
cat >> "$rootfs/opt/start-mm2.sh" << 'START' cat >> "$rootfs/opt/start-mm2.sh" << 'START'
export PATH="/usr/local/bin:/usr/bin:/bin:$PATH" export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"
export NODE_ENV=production export NODE_ENV=production
export MM_PORT="${MM2_PORT:-8082}" export MM_PORT="${MM2_PORT:-8085}"
export MM_ADDRESS="${MM2_ADDRESS:-0.0.0.0}" export MM_ADDRESS="${MM2_ADDRESS:-0.0.0.0}"
MM_DIR="/opt/magic_mirror" MM_DIR="/opt/magic_mirror"

View File

@ -1200,49 +1200,47 @@ case "$1" in
addr=$(echo "$local" | sed 's/:[^:]*$//') addr=$(echo "$local" | sed 's/:[^:]*$//')
name=""; icon=""; category="other"; path="" name=""; icon=""; category="other"; path=""
# First: identify by process name (most accurate) # First: identify by well-known port (most reliable for multi-service ports)
case "$proc" in case "$port" in
sshd|dropbear) name="SSH"; icon="lock"; category="system" ;; 22) name="SSH"; icon="lock"; category="system" ;;
dnsmasq|named|unbound) name="DNS"; icon="globe"; category="system" ;; 53) name="DNS"; icon="globe"; category="system" ;;
haproxy) name="HAProxy"; icon="arrow"; category="proxy" ;; 80) name="HTTP"; icon="arrow"; path="/"; category="proxy" ;;
nginx|uhttpd) name="Web Server"; icon="settings"; category="system" ;; 443) name="HTTPS"; icon="shield"; path="/"; category="proxy" ;;
gitea) name="Gitea"; icon="git"; path=":$port"; category="app" ;; 2222) name="Gitea SSH"; icon="git"; category="app" ;;
hexo|node) [ "$port" = "4000" ] && { name="HexoJS"; icon="blog"; path=":$port"; category="app"; } ;; 3000) name="Gitea"; icon="git"; path=":3000"; category="app" ;;
crowdsec|lapi) name="CrowdSec"; icon="security"; category="security" ;; 3483) name="Squeezebox"; icon="music"; category="media" ;;
netifyd) name="Netifyd"; icon="chart"; path=":$port"; category="monitoring" ;; 4000) name="HexoJS"; icon="blog"; path=":4000"; category="app" ;;
streamlit|python*) 6060) name="CrowdSec LAPI"; icon="security"; category="security" ;;
[ "$port" = "8501" ] && { name="Streamlit"; icon="app"; path=":$port"; category="app"; } 8081) name="LuCI"; icon="settings"; path=":8081"; category="system" ;;
;; 8085) name="MagicMirror2"; icon="app"; path=":8085"; category="app" ;;
slimserver|squeezeboxserver) name="Lyrion"; icon="music"; path=":$port"; category="media" ;; 8086) name="Netifyd"; icon="chart"; path=":8086"; category="monitoring" ;;
tor) name="Tor"; icon="onion"; category="privacy" ;; 8404) name="HAProxy Stats"; icon="stats"; path=":8404/stats"; category="monitoring" ;;
cyberfeed*) name="CyberFeed"; icon="feed"; path=":$port"; category="app" ;; 8444) name="LuCI HTTPS"; icon="admin"; path=":8444"; category="system" ;;
metabolizer*) name="Metabolizer"; icon="blog"; path=":$port"; category="app" ;; 8501) name="Streamlit"; icon="app"; path=":8501"; category="app" ;;
magicmirror*|electron) name="MagicMirror"; icon="app"; path=":$port"; category="app" ;; 9000) name="Lyrion"; icon="music"; path=":9000"; category="media" ;;
picobrew*) name="PicoBrew"; icon="app"; path=":$port"; category="app" ;; 9050) name="Tor SOCKS"; icon="onion"; category="privacy" ;;
9090) name="Lyrion CLI"; icon="music"; category="media" ;;
esac esac
# Fallback: identify by port number if process didn't match # Fallback: identify by process name if port didn't match
if [ -z "$name" ]; then if [ -z "$name" ]; then
case "$port" in case "$proc" in
22) name="SSH"; icon="lock"; category="system" ;; sshd|dropbear) name="SSH"; icon="lock"; category="system" ;;
53) name="DNS"; icon="globe"; category="system" ;; dnsmasq|named|unbound) name="DNS"; icon="globe"; category="system" ;;
80) name="HTTP"; icon="arrow"; path="/"; category="proxy" ;; haproxy) name="HAProxy"; icon="arrow"; category="proxy" ;;
443) name="HTTPS"; icon="shield"; path="/"; category="proxy" ;; nginx|uhttpd) name="Web Server"; icon="settings"; category="system" ;;
3000) name="Gitea"; icon="git"; path=":3000"; category="app" ;; gitea) name="Gitea"; icon="git"; path=":$port"; category="app" ;;
4000) name="HexoJS"; icon="blog"; path=":4000"; category="app" ;; hexo|node) name="HexoJS"; icon="blog"; path=":$port"; category="app" ;;
6060) name="CrowdSec LAPI"; icon="security"; category="security" ;; crowdsec|lapi) name="CrowdSec"; icon="security"; category="security" ;;
8080) name="Web App"; icon="app"; path=":8080"; category="app" ;; netifyd) name="Netifyd"; icon="chart"; path=":$port"; category="monitoring" ;;
8081) name="LuCI"; icon="settings"; path=":8081"; category="system" ;; slimserver|squeezeboxserver) name="Lyrion"; icon="music"; path=":$port"; category="media" ;;
8082) name="CyberFeed"; icon="feed"; path=":8082"; category="app" ;; tor) name="Tor"; icon="onion"; category="privacy" ;;
8086) name="Netifyd"; icon="chart"; path=":8086"; category="monitoring" ;; cyberfeed*) name="CyberFeed"; icon="feed"; path=":$port"; category="app" ;;
8404) name="HAProxy Stats"; icon="stats"; path=":8404/stats"; category="monitoring" ;; metabolizer*) name="Metabolizer"; icon="blog"; path=":$port"; category="app" ;;
8444) name="LuCI HTTPS"; icon="admin"; path=":8444"; category="system" ;; magicmirror*|electron) name="MagicMirror"; icon="app"; path=":$port"; category="app" ;;
8501) name="Streamlit"; icon="app"; path=":8501"; category="app" ;; picobrew*) name="PicoBrew"; icon="app"; path=":$port"; category="app" ;;
9000) name="Lyrion"; icon="music"; path=":9000"; category="media" ;; streamlit) name="Streamlit"; icon="app"; path=":$port"; category="app" ;;
9050) name="Tor SOCKS"; icon="onion"; category="privacy" ;; python*) name="Python App"; icon="app"; path=":$port"; category="app" ;;
9090) name="Lyrion CLI"; icon="music"; category="media" ;;
2222) name="Gitea SSH"; icon="git"; category="app" ;;
3483) name="Squeezebox"; icon="music"; category="media" ;;
*) name="$proc"; icon=""; category="other"; path=":$port" ;; *) name="$proc"; icon=""; category="other"; path=":$port" ;;
esac esac
fi fi