feat(rtty): Add remote package installation for mesh nodes

Add rttyctl commands for remote package deployment:
- rttyctl install <node|all> <app_id> - Install package on node(s)
- rttyctl install-status <node> [app] - Check package status
- rttyctl deploy-ttyd <node|all> - Deploy ttyd web terminal

RPCD methods added:
- install_remote, install_mesh, deploy_ttyd, install_status

Features:
- Node discovery from master-link, WireGuard, P2P mesh
- Auto-enables and starts ttyd after installation
- Batch install with summary stats (installed/skipped/failed)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-03-15 15:10:32 +01:00
parent e2723946d4
commit ac7912e0a1
5 changed files with 401 additions and 5 deletions

View File

@ -5232,3 +5232,15 @@ git checkout HEAD -- index.html
- Fixed protocol display bug ("TCPnull" → "TCP") - Fixed protocol display bug ("TCPnull" → "TCP")
- Removed mitmproxy-out container (not needed for WAF) - Removed mitmproxy-out container (not needed for WAF)
- Updated MITM detection to check mitmproxy-in specifically - Updated MITM detection to check mitmproxy-in specifically
### 2026-03-16
- **Remote ttyd Deployment for Mesh Nodes (Complete)**
- CLI commands: `rttyctl install <node|all> <app>`, `rttyctl install-status`, `rttyctl deploy-ttyd`
- Installs packages on remote mesh nodes via RPC proxy to AppStore
- Auto-enables and starts ttyd service after installation
- `rttyctl install all <app>` - batch install across all mesh nodes
- Node discovery from: master-link peers, WireGuard endpoints, P2P mesh
- 4 new RPCD methods: install_remote, install_mesh, deploy_ttyd, install_status
- ACL permissions updated for remote installation write actions
- Use case: Deploy ttyd web terminal to all SecuBox nodes for browser-based SSH

View File

@ -10,6 +10,15 @@ _Last updated: 2026-03-16 (DPI LAN Passive Analysis)_
### 2026-03-16 ### 2026-03-16
- **Remote ttyd Deployment for Mesh Nodes (Complete)**
- CLI commands: `rttyctl install`, `rttyctl install-status`, `rttyctl deploy-ttyd`
- Installs packages on remote mesh nodes via RPC proxy
- Auto-enables and starts ttyd service after installation
- `rttyctl install all <app>` - batch install across all mesh nodes
- Node discovery from: master-link, WireGuard peers, P2P mesh
- 4 new RPCD methods: install_remote, install_mesh, deploy_ttyd, install_status
- ACL permissions updated for remote installation actions
- **Dual-Stream DPI Phase 4 - LAN Passive Flow Analysis (Complete)** - **Dual-Stream DPI Phase 4 - LAN Passive Flow Analysis (Complete)**
- New `dpi-lan-collector` daemon for passive br-lan monitoring - New `dpi-lan-collector` daemon for passive br-lan monitoring
- Zero MITM, zero caching - pure nDPI/conntrack flow observation - Zero MITM, zero caching - pure nDPI/conntrack flow observation
@ -626,8 +635,8 @@ _Last updated: 2026-03-16 (DPI LAN Passive Analysis)_
### v1.0 Release Prep ### v1.0 Release Prep
1. **Remote ttyd Deployment** - Auto-install ttyd on mesh nodes 1. **Device Provisioning** - Use Config Vault export-clone for SecuBox replication
2. **Device Provisioning** - Use Config Vault export-clone for SecuBox replication 2. **LuCI Remote Install Button** - Add "Deploy ttyd" action to Remote Control dashboard (optional)
### v1.1+ Extended Mesh ### v1.1+ Extended Mesh

View File

@ -438,6 +438,126 @@ method_token_rpc() {
fi fi
} }
#------------------------------------------------------------------------------
# Remote Package Installation
#------------------------------------------------------------------------------
# Install package on remote node
method_install_remote() {
local node_id app_id
read -r input
json_load "$input"
json_get_var node_id node_id
json_get_var app_id app_id
[ -z "$node_id" ] || [ -z "$app_id" ] && {
echo '{"success":false,"error":"Missing node_id or app_id"}'
return
}
# Execute via rttyctl
local result=$($RTTYCTL install "$node_id" "$app_id" 2>&1)
local rc=$?
json_init
if [ $rc -eq 0 ] && echo "$result" | grep -q "✓"; then
json_add_boolean "success" 1
json_add_string "message" "Package $app_id installed on $node_id"
json_add_string "output" "$result"
else
json_add_boolean "success" 0
json_add_string "error" "$result"
fi
json_dump
}
# Install package on all mesh nodes
method_install_mesh() {
local app_id
read -r input
json_load "$input"
json_get_var app_id app_id
[ -z "$app_id" ] && {
echo '{"success":false,"error":"Missing app_id"}'
return
}
# Execute via rttyctl with 'all' target
local result=$($RTTYCTL install all "$app_id" 2>&1)
local rc=$?
# Parse summary line
local installed=$(echo "$result" | grep "Summary:" | sed 's/.*: //' | cut -d, -f1 | awk '{print $1}')
local skipped=$(echo "$result" | grep "Summary:" | sed 's/.*: //' | cut -d, -f2 | awk '{print $1}')
local failed=$(echo "$result" | grep "Summary:" | sed 's/.*: //' | cut -d, -f3 | awk '{print $1}')
json_init
json_add_boolean "success" 1
json_add_string "app_id" "$app_id"
json_add_int "installed" "${installed:-0}"
json_add_int "skipped" "${skipped:-0}"
json_add_int "failed" "${failed:-0}"
json_add_string "output" "$result"
json_dump
}
# Deploy ttyd to node(s) - shortcut
method_deploy_ttyd() {
local target
read -r input
json_load "$input"
json_get_var target target
[ -z "$target" ] && {
echo '{"success":false,"error":"Missing target (node_id or \"all\")"}'
return
}
# Execute via rttyctl
local result=$($RTTYCTL deploy-ttyd "$target" 2>&1)
local rc=$?
json_init
if [ $rc -eq 0 ] && echo "$result" | grep -q "✓"; then
json_add_boolean "success" 1
json_add_string "message" "ttyd deployed to $target"
# If single node, include ttyd URL
if [ "$target" != "all" ]; then
local addr=$($RTTYCTL node "$target" 2>&1 | grep "Address:" | awk '{print $2}')
[ -z "$addr" ] && addr="$target"
json_add_string "terminal_url" "http://${addr}:7681/"
fi
else
json_add_boolean "success" 0
json_add_string "error" "$result"
fi
json_dump
}
# Check package status on remote node
method_install_status() {
local node_id app_id
read -r input
json_load "$input"
json_get_var node_id node_id
json_get_var app_id app_id
[ -z "$node_id" ] && {
echo '{"success":false,"error":"Missing node_id"}'
return
}
local result=$($RTTYCTL install-status "$node_id" "$app_id" 2>&1)
json_init
json_add_boolean "success" 1
json_add_string "node_id" "$node_id"
json_add_string "output" "$result"
json_dump
}
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
# Avatar-Tap Session Integration # Avatar-Tap Session Integration
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
@ -621,7 +741,11 @@ case "$1" in
"replay_to_node": {"session_id": "integer", "target_node": "string"}, "replay_to_node": {"session_id": "integer", "target_node": "string"},
"export_session": {"session_id": "integer"}, "export_session": {"session_id": "integer"},
"import_session": {"content": "string"}, "import_session": {"content": "string"},
"get_tap_status": {} "get_tap_status": {},
"install_remote": {"node_id": "string", "app_id": "string"},
"install_mesh": {"app_id": "string"},
"deploy_ttyd": {"target": "string"},
"install_status": {"node_id": "string", "app_id": "string"}
} }
EOF EOF
;; ;;
@ -651,6 +775,10 @@ EOF
export_session) method_export_session ;; export_session) method_export_session ;;
import_session) method_import_session ;; import_session) method_import_session ;;
get_tap_status) method_get_tap_status ;; get_tap_status) method_get_tap_status ;;
install_remote) method_install_remote ;;
install_mesh) method_install_mesh ;;
deploy_ttyd) method_deploy_ttyd ;;
install_status) method_install_status ;;
*) *)
echo '{"error":"Unknown method"}' echo '{"error":"Unknown method"}'
;; ;;

View File

@ -14,7 +14,8 @@
"token_validate", "token_validate",
"get_tap_sessions", "get_tap_sessions",
"get_tap_session", "get_tap_session",
"get_tap_status" "get_tap_status",
"install_status"
] ]
}, },
"uci": ["rtty-remote", "avatar-tap"] "uci": ["rtty-remote", "avatar-tap"]
@ -34,7 +35,10 @@
"token_rpc", "token_rpc",
"replay_to_node", "replay_to_node",
"export_session", "export_session",
"import_session" "import_session",
"install_remote",
"install_mesh",
"deploy_ttyd"
] ]
}, },
"uci": ["rtty-remote"] "uci": ["rtty-remote"]

View File

@ -936,6 +936,235 @@ cmd_tap_json_session() {
"SELECT id, domain, path, method, captured_at, last_used, use_count, label FROM sessions WHERE id=$session_id" 2>/dev/null || echo '{"error":"Session not found"}' "SELECT id, domain, path, method, captured_at, last_used, use_count, label FROM sessions WHERE id=$session_id" 2>/dev/null || echo '{"error":"Session not found"}'
} }
#------------------------------------------------------------------------------
# Remote Package Installation
#------------------------------------------------------------------------------
cmd_install() {
local target="$1"
local app_id="$2"
[ -z "$target" ] || [ -z "$app_id" ] && {
echo "Usage: rttyctl install <node|all> <app_id>"
echo ""
echo "Install a package on remote mesh node(s)."
echo ""
echo "Examples:"
echo " rttyctl install 192.168.255.2 ttyd"
echo " rttyctl install sb-office ttyd"
echo " rttyctl install all ttyd # Install on ALL mesh nodes"
echo ""
echo "Common apps: ttyd, crowdsec, netifyd, haproxy"
exit 1
}
if [ "$target" = "all" ]; then
cmd_install_all "$app_id"
return $?
fi
local addr=$(get_node_address "$target")
[ -z "$addr" ] && die "Cannot resolve node address for: $target"
echo "Installing '$app_id' on $target ($addr)..."
echo ""
# Check if node is reachable
if ! ping -c 1 -W 2 "$addr" >/dev/null 2>&1; then
die "Node $target ($addr) is not reachable"
fi
# Check if already installed
echo "Checking if already installed..."
local check_result=$(cmd_rpc "$target" "luci.secubox" "check_package" "{\"package\":\"$app_id\"}" 2>/dev/null)
local installed=$(echo "$check_result" | jsonfilter -e '@.installed' 2>/dev/null)
if [ "$installed" = "true" ] || [ "$installed" = "1" ]; then
echo "Package '$app_id' is already installed on $target"
return 0
fi
# Install via remote RPC
echo "Installing package..."
local install_result=$(cmd_rpc "$target" "luci.secubox" "install_appstore_app" "{\"app_id\":\"$app_id\"}" 2>&1)
local rc=$?
if [ $rc -eq 0 ]; then
local success=$(echo "$install_result" | jsonfilter -e '@.success' 2>/dev/null)
if [ "$success" = "true" ] || [ "$success" = "1" ]; then
echo "✓ Successfully installed '$app_id' on $target"
log "info" "Installed $app_id on $target ($addr)"
# If ttyd, enable and start the service
if [ "$app_id" = "ttyd" ]; then
echo ""
echo "Enabling and starting ttyd service..."
cmd_rpc "$target" "luci" "setInitAction" '{"name":"ttyd","action":"enable"}' >/dev/null 2>&1
cmd_rpc "$target" "luci" "setInitAction" '{"name":"ttyd","action":"start"}' >/dev/null 2>&1
echo "✓ ttyd service enabled and started"
echo ""
echo "Web terminal available at: http://$addr:7681/"
fi
else
local error=$(echo "$install_result" | jsonfilter -e '@.error' 2>/dev/null)
echo "✗ Installation failed: ${error:-Unknown error}"
echo "Raw response: $install_result"
return 1
fi
else
echo "✗ RPC call failed"
echo "Response: $install_result"
return 1
fi
}
cmd_install_all() {
local app_id="$1"
[ -z "$app_id" ] && die "Usage: rttyctl install all <app_id>"
echo "Installing '$app_id' on ALL mesh nodes..."
echo "========================================="
echo ""
local success_count=0
local fail_count=0
local skip_count=0
# Get all nodes from various sources
local nodes=""
# From master-link
if command -v ml_peer_list >/dev/null 2>&1; then
local ml_nodes=$(ml_peer_list 2>/dev/null | jsonfilter -e '@.peers[*].address' 2>/dev/null)
nodes="$nodes $ml_nodes"
fi
# From WireGuard peers
if command -v wg >/dev/null 2>&1; then
local wg_nodes=$(wg show all endpoints 2>/dev/null | awk '{print $2}' | cut -d: -f1)
nodes="$nodes $wg_nodes"
fi
# From P2P mesh
if [ -x /usr/sbin/secubox-p2p ]; then
local p2p_nodes=$(/usr/sbin/secubox-p2p peers --json 2>/dev/null | jsonfilter -e '@[*].address' 2>/dev/null)
nodes="$nodes $p2p_nodes"
fi
# Deduplicate and process
local unique_nodes=$(echo "$nodes" | tr ' ' '\n' | sort -u | grep -v '^$')
local total=$(echo "$unique_nodes" | wc -l)
echo "Found $total mesh nodes"
echo ""
for node in $unique_nodes; do
[ -z "$node" ] && continue
echo "[$node]"
# Skip localhost
case "$node" in
127.0.0.1|localhost|$(uci -q get network.lan.ipaddr))
echo " Skipping (local node)"
skip_count=$((skip_count + 1))
continue
;;
esac
# Check reachability
if ! ping -c 1 -W 2 "$node" >/dev/null 2>&1; then
echo " ✗ Not reachable"
fail_count=$((fail_count + 1))
continue
fi
# Try to install
local result=$(cmd_rpc "$node" "luci.secubox" "install_appstore_app" "{\"app_id\":\"$app_id\"}" 2>&1)
local success=$(echo "$result" | jsonfilter -e '@.success' 2>/dev/null)
if [ "$success" = "true" ] || [ "$success" = "1" ]; then
echo " ✓ Installed"
success_count=$((success_count + 1))
# Enable ttyd if that's what we installed
if [ "$app_id" = "ttyd" ]; then
cmd_rpc "$node" "luci" "setInitAction" '{"name":"ttyd","action":"enable"}' >/dev/null 2>&1
cmd_rpc "$node" "luci" "setInitAction" '{"name":"ttyd","action":"start"}' >/dev/null 2>&1
echo " ✓ ttyd enabled and started"
fi
else
local error=$(echo "$result" | jsonfilter -e '@.error' 2>/dev/null)
if echo "$error" | grep -qi "already installed"; then
echo " ⊘ Already installed"
skip_count=$((skip_count + 1))
else
echo " ✗ Failed: ${error:-Unknown error}"
fail_count=$((fail_count + 1))
fi
fi
done
echo ""
echo "Summary: $success_count installed, $skip_count skipped, $fail_count failed"
log "info" "Mesh install of $app_id: $success_count ok, $skip_count skip, $fail_count fail"
}
cmd_install_status() {
local target="$1"
local app_id="$2"
[ -z "$target" ] && {
echo "Usage: rttyctl install-status <node> [app_id]"
exit 1
}
local addr=$(get_node_address "$target")
[ -z "$addr" ] && die "Cannot resolve node address for: $target"
echo "Package Status on $target ($addr)"
echo "=================================="
if [ -n "$app_id" ]; then
# Check specific package
local result=$(cmd_rpc "$target" "luci.secubox" "check_package" "{\"package\":\"$app_id\"}" 2>/dev/null)
local installed=$(echo "$result" | jsonfilter -e '@.installed' 2>/dev/null)
local version=$(echo "$result" | jsonfilter -e '@.version' 2>/dev/null)
if [ "$installed" = "true" ] || [ "$installed" = "1" ]; then
echo " $app_id: installed (v${version:-?})"
else
echo " $app_id: not installed"
fi
else
# List all SecuBox packages
local result=$(cmd_rpc "$target" "luci.secubox" "list_installed" '{}' 2>/dev/null)
echo "$result" | jsonfilter -e '@.packages[*]' 2>/dev/null | while read pkg; do
local name=$(echo "$pkg" | jsonfilter -e '@.name')
local ver=$(echo "$pkg" | jsonfilter -e '@.version')
echo " $name: $ver"
done
fi
}
cmd_deploy_ttyd() {
local target="$1"
[ -z "$target" ] && {
echo "Usage: rttyctl deploy-ttyd <node|all>"
echo ""
echo "Deploy ttyd web terminal to mesh node(s)."
echo "This is a shortcut for: rttyctl install <target> ttyd"
exit 1
}
echo "Deploying ttyd Web Terminal"
echo "==========================="
echo ""
cmd_install "$target" "ttyd"
}
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
# Server Management # Server Management
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
@ -1048,6 +1277,11 @@ RPCD Proxy:
rpc-list <node> List available RPCD objects rpc-list <node> List available RPCD objects
rpc-batch <node> <file.json> Execute batch RPCD calls rpc-batch <node> <file.json> Execute batch RPCD calls
Remote Package Installation:
install <node|all> <app_id> Install package on node(s)
install-status <node> [app] Check package installation status
deploy-ttyd <node|all> Deploy ttyd web terminal (shortcut)
Avatar-Tap Session Replay: Avatar-Tap Session Replay:
tap-sessions [domain] List captured sessions (filter by domain) tap-sessions [domain] List captured sessions (filter by domain)
tap-show <id> Show session details (headers, cookies) tap-show <id> Show session details (headers, cookies)
@ -1116,6 +1350,15 @@ case "$1" in
rpc-batch) rpc-batch)
cmd_rpc_batch "$2" "$3" cmd_rpc_batch "$2" "$3"
;; ;;
install)
cmd_install "$2" "$3"
;;
install-status)
cmd_install_status "$2" "$3"
;;
deploy-ttyd)
cmd_deploy_ttyd "$2"
;;
sessions) sessions)
cmd_sessions "$2" cmd_sessions "$2"
;; ;;