From ac7912e0a1f0f7e4120ea4d0b63fcbce7dcccb4d Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Sun, 15 Mar 2026 15:10:32 +0100 Subject: [PATCH] feat(rtty): Add remote package installation for mesh nodes Add rttyctl commands for remote package deployment: - rttyctl install - Install package on node(s) - rttyctl install-status [app] - Check package status - rttyctl deploy-ttyd - 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 --- .claude/HISTORY.md | 12 + .claude/WIP.md | 13 +- .../root/usr/libexec/rpcd/luci.rtty-remote | 130 +++++++++- .../share/rpcd/acl.d/luci-rtty-remote.json | 8 +- .../files/usr/sbin/rttyctl | 243 ++++++++++++++++++ 5 files changed, 401 insertions(+), 5 deletions(-) diff --git a/.claude/HISTORY.md b/.claude/HISTORY.md index 719d582f..1342955b 100644 --- a/.claude/HISTORY.md +++ b/.claude/HISTORY.md @@ -5232,3 +5232,15 @@ git checkout HEAD -- index.html - Fixed protocol display bug ("TCPnull" → "TCP") - Removed mitmproxy-out container (not needed for WAF) - Updated MITM detection to check mitmproxy-in specifically + +### 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 to AppStore + - Auto-enables and starts ttyd service after installation + - `rttyctl install all ` - 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 diff --git a/.claude/WIP.md b/.claude/WIP.md index 756e13a8..e02a2b7f 100644 --- a/.claude/WIP.md +++ b/.claude/WIP.md @@ -10,6 +10,15 @@ _Last updated: 2026-03-16 (DPI LAN Passive Analysis)_ ### 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 ` - 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)** - New `dpi-lan-collector` daemon for passive br-lan monitoring - 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 -1. **Remote ttyd Deployment** - Auto-install ttyd on mesh nodes -2. **Device Provisioning** - Use Config Vault export-clone for SecuBox replication +1. **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 diff --git a/package/secubox/luci-app-rtty-remote/root/usr/libexec/rpcd/luci.rtty-remote b/package/secubox/luci-app-rtty-remote/root/usr/libexec/rpcd/luci.rtty-remote index 9d5e91c9..7bde0761 100644 --- a/package/secubox/luci-app-rtty-remote/root/usr/libexec/rpcd/luci.rtty-remote +++ b/package/secubox/luci-app-rtty-remote/root/usr/libexec/rpcd/luci.rtty-remote @@ -438,6 +438,126 @@ method_token_rpc() { 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 #------------------------------------------------------------------------------ @@ -621,7 +741,11 @@ case "$1" in "replay_to_node": {"session_id": "integer", "target_node": "string"}, "export_session": {"session_id": "integer"}, "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 ;; @@ -651,6 +775,10 @@ EOF export_session) method_export_session ;; import_session) method_import_session ;; 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"}' ;; diff --git a/package/secubox/luci-app-rtty-remote/root/usr/share/rpcd/acl.d/luci-rtty-remote.json b/package/secubox/luci-app-rtty-remote/root/usr/share/rpcd/acl.d/luci-rtty-remote.json index 5e0daac4..045f2e49 100644 --- a/package/secubox/luci-app-rtty-remote/root/usr/share/rpcd/acl.d/luci-rtty-remote.json +++ b/package/secubox/luci-app-rtty-remote/root/usr/share/rpcd/acl.d/luci-rtty-remote.json @@ -14,7 +14,8 @@ "token_validate", "get_tap_sessions", "get_tap_session", - "get_tap_status" + "get_tap_status", + "install_status" ] }, "uci": ["rtty-remote", "avatar-tap"] @@ -34,7 +35,10 @@ "token_rpc", "replay_to_node", "export_session", - "import_session" + "import_session", + "install_remote", + "install_mesh", + "deploy_ttyd" ] }, "uci": ["rtty-remote"] diff --git a/package/secubox/secubox-app-rtty-remote/files/usr/sbin/rttyctl b/package/secubox/secubox-app-rtty-remote/files/usr/sbin/rttyctl index d68a454a..6e03adb7 100644 --- a/package/secubox/secubox-app-rtty-remote/files/usr/sbin/rttyctl +++ b/package/secubox/secubox-app-rtty-remote/files/usr/sbin/rttyctl @@ -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"}' } +#------------------------------------------------------------------------------ +# Remote Package Installation +#------------------------------------------------------------------------------ + +cmd_install() { + local target="$1" + local app_id="$2" + + [ -z "$target" ] || [ -z "$app_id" ] && { + echo "Usage: rttyctl install " + 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 " + + 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 [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 " + echo "" + echo "Deploy ttyd web terminal to mesh node(s)." + echo "This is a shortcut for: rttyctl install ttyd" + exit 1 + } + + echo "Deploying ttyd Web Terminal" + echo "===========================" + echo "" + + cmd_install "$target" "ttyd" +} + #------------------------------------------------------------------------------ # Server Management #------------------------------------------------------------------------------ @@ -1048,6 +1277,11 @@ RPCD Proxy: rpc-list List available RPCD objects rpc-batch Execute batch RPCD calls +Remote Package Installation: + install Install package on node(s) + install-status [app] Check package installation status + deploy-ttyd Deploy ttyd web terminal (shortcut) + Avatar-Tap Session Replay: tap-sessions [domain] List captured sessions (filter by domain) tap-show Show session details (headers, cookies) @@ -1116,6 +1350,15 @@ case "$1" in rpc-batch) cmd_rpc_batch "$2" "$3" ;; + install) + cmd_install "$2" "$3" + ;; + install-status) + cmd_install_status "$2" "$3" + ;; + deploy-ttyd) + cmd_deploy_ttyd "$2" + ;; sessions) cmd_sessions "$2" ;;