feat: app script_navegador y dashboard Metabase
App Go para ejecutar scripts de navegación automatizada usando las funciones CDP del registry. Incluye script de creación de dashboard en Metabase para monitoreo. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// getWindowsHostIP obtiene la IP del host Windows desde WSL2.
|
||||
// Lee /etc/resolv.conf que WSL2 configura con la IP del host.
|
||||
func getWindowsHostIP() string {
|
||||
data, err := os.ReadFile("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "nameserver ") {
|
||||
ip := strings.TrimPrefix(line, "nameserver ")
|
||||
ip = strings.TrimSpace(ip)
|
||||
if ip != "" {
|
||||
return ip
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getWindowsGatewayIP obtiene la IP del gateway (host Windows) desde la tabla de rutas.
|
||||
func getWindowsGatewayIP() string {
|
||||
data, err := os.ReadFile("/proc/net/route")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 3 && fields[1] == "00000000" { // default route
|
||||
hexIP := fields[2]
|
||||
if len(hexIP) == 8 {
|
||||
// /proc/net/route stores IPs as little-endian 32-bit hex
|
||||
// "011017AC" -> bytes [01,10,17,AC] -> IP 172.23.16.1 (reversed)
|
||||
var a, b, c, d uint8
|
||||
fmt.Sscanf(hexIP[0:2], "%02x", &a)
|
||||
fmt.Sscanf(hexIP[2:4], "%02x", &b)
|
||||
fmt.Sscanf(hexIP[4:6], "%02x", &c)
|
||||
fmt.Sscanf(hexIP[6:8], "%02x", &d)
|
||||
return fmt.Sprintf("%d.%d.%d.%d", d, c, b, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// waitForCDP espera a que el puerto CDP esté accesible desde WSL.
|
||||
func waitForCDP(host string, port int, timeout time.Duration) error {
|
||||
deadline := time.Now().Add(timeout)
|
||||
addr := fmt.Sprintf("%s:%d", host, port)
|
||||
for time.Now().Before(deadline) {
|
||||
conn, err := net.DialTimeout("tcp", addr, 300*time.Millisecond)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}
|
||||
return fmt.Errorf("CDP %s no disponible despues de %s", addr, timeout)
|
||||
}
|
||||
|
||||
// startCDPProxy levanta un proxy TCP local que reenvía conexiones al host Windows.
|
||||
// Chrome CDP solo acepta conexiones desde localhost, así que el proxy en WSL
|
||||
// conecta al host Windows vía portproxy/netsh y expone el puerto localmente.
|
||||
// Retorna el puerto local del proxy y una función para cerrarlo.
|
||||
func startCDPProxy(windowsHost string, remotePort, localPort int) (net.Listener, error) {
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", localPort))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proxy listen: %w", err)
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
client, err := ln.Accept()
|
||||
if err != nil {
|
||||
return // listener cerrado
|
||||
}
|
||||
go proxyConn(client, windowsHost, remotePort)
|
||||
}
|
||||
}()
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func proxyConn(client net.Conn, host string, port int) {
|
||||
defer client.Close()
|
||||
remote, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), 5*time.Second)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer remote.Close()
|
||||
go io.Copy(remote, client)
|
||||
io.Copy(client, remote)
|
||||
}
|
||||
Reference in New Issue
Block a user