fix(infra): gradle_run detecta android-sdk — issue 0076 #2

Open
dataforge wants to merge 538 commits from auto/0076-gradle-sdk-detect into master
26 changed files with 1076 additions and 0 deletions
Showing only changes of commit c33e907fef - Show all commits
+37
View File
@@ -0,0 +1,37 @@
---
name: install_nordvpn
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "install_nordvpn() -> void"
description: "Instala NordVPN CLI en Ubuntu/Debian (incluido WSL2). Configura repositorio oficial, instala paquete y habilita servicio nordvpnd. Idempotente."
tags: [vpn, nordvpn, install, infra, wsl2]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/install_nordvpn.sh"
---
## Ejemplo
```bash
source install_nordvpn.sh
install_nordvpn
# nordvpn ya instalado: NordVPN Version 3.x.x
# — o —
# Instalando NordVPN CLI...
# NordVPN instalado: NordVPN Version 3.x.x
# NOTA: ejecuta 'nordvpn login' para autenticarte
```
## Notas
Usa el script de instalacion oficial de NordVPN. En WSL2 sin systemd, levanta nordvpnd manualmente. Agrega el usuario al grupo nordvpn para evitar sudo en comandos posteriores. Despues de instalar, se requiere `nordvpn login` para autenticarse.
+41
View File
@@ -0,0 +1,41 @@
# install_nordvpn
# ---------------
# Instala NordVPN CLI en Ubuntu/Debian (incluido WSL2).
# Configura el repositorio oficial, instala el paquete y habilita el servicio.
# Si ya esta instalado, no hace nada.
#
# USO (sourced):
# source install_nordvpn.sh
# install_nordvpn
install_nordvpn() {
if command -v nordvpn &>/dev/null; then
echo "nordvpn ya instalado: $(nordvpn version 2>/dev/null)"
return 0
fi
echo "Instalando NordVPN CLI..."
# Descargar e instalar via script oficial
sh <(curl -sSf https://downloads.nordcdn.com/apps/linux/install.sh) 2>&1
if ! command -v nordvpn &>/dev/null; then
echo "install_nordvpn: fallo la instalacion" >&2
return 1
fi
# Agregar usuario al grupo nordvpn para evitar sudo
sudo usermod -aG nordvpn "$USER" 2>/dev/null || true
# Habilitar servicio (systemd o manual para WSL2)
if command -v systemctl &>/dev/null && systemctl is-system-running &>/dev/null 2>&1; then
sudo systemctl enable --now nordvpnd 2>/dev/null || true
else
# WSL2 sin systemd — levantar daemon manualmente
sudo nordvpnd &>/dev/null &
sleep 2
fi
echo "NordVPN instalado: $(nordvpn version 2>/dev/null)"
echo "NOTA: ejecuta 'nordvpn login' para autenticarte"
}
+40
View File
@@ -0,0 +1,40 @@
---
name: nordvpn_connect
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "nordvpn_connect(country?: string, city?: string) -> json"
description: "Conecta a NordVPN por pais, ciudad o servidor especifico. Sin argumentos conecta al mejor servidor disponible. Devuelve JSON con resultado."
tags: [vpn, nordvpn, connect, infra, network]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/nordvpn_connect.sh"
---
## Ejemplo
```bash
source nordvpn_connect.sh
nordvpn_connect
# {"ok":true,"server":"us1234.nordvpn.com","country":"auto","city":"auto"}
nordvpn_connect Spain
# {"ok":true,"server":"es42.nordvpn.com","country":"Spain","city":"auto"}
nordvpn_connect Spain Madrid
# {"ok":true,"server":"es15.nordvpn.com","country":"Spain","city":"Madrid"}
```
## Notas
Requiere NordVPN CLI instalado y autenticado (`nordvpn login`). La salida JSON facilita composicion con otros scripts y pipelines. Si ya hay una conexion activa, NordVPN reconecta automaticamente al nuevo destino.
+39
View File
@@ -0,0 +1,39 @@
# nordvpn_connect
# ---------------
# Conecta a NordVPN. Acepta pais, ciudad o servidor especifico.
# Sin argumentos conecta al mejor servidor disponible.
# Imprime JSON con el resultado de la conexion.
#
# USO (sourced):
# source nordvpn_connect.sh
# nordvpn_connect # mejor servidor
# nordvpn_connect Spain # por pais
# nordvpn_connect Spain Madrid # por ciudad
# nordvpn_connect Spain '#42' # servidor especifico
nordvpn_connect() {
local country="${1:-}"
local city="${2:-}"
if ! command -v nordvpn &>/dev/null; then
echo '{"ok":false,"error":"nordvpn no instalado"}' >&2
return 1
fi
local args=()
[ -n "$country" ] && args+=("$country")
[ -n "$city" ] && args+=("$city")
local output
output=$(nordvpn connect "${args[@]}" 2>&1)
local rc=$?
if [ $rc -eq 0 ] && echo "$output" | grep -qi "connected"; then
local server
server=$(echo "$output" | grep -oP '(?<=to )\S+' | head -1)
echo "{\"ok\":true,\"server\":\"${server}\",\"country\":\"${country:-auto}\",\"city\":\"${city:-auto}\"}"
else
echo "{\"ok\":false,\"error\":$(echo "$output" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip()))' 2>/dev/null || echo "\"$output\"")}"
return 1
fi
}
@@ -0,0 +1,34 @@
---
name: nordvpn_disconnect
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "nordvpn_disconnect() -> json"
description: "Desconecta de NordVPN. Idempotente — si no hay conexion activa retorna ok. Devuelve JSON con resultado."
tags: [vpn, nordvpn, disconnect, infra, network]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/nordvpn_disconnect.sh"
---
## Ejemplo
```bash
source nordvpn_disconnect.sh
nordvpn_disconnect
# {"ok":true,"status":"disconnected"}
```
## Notas
Idempotente: si no hay conexion activa, retorna ok sin error. Requiere NordVPN CLI instalado.
@@ -0,0 +1,26 @@
# nordvpn_disconnect
# ------------------
# Desconecta de NordVPN. Idempotente — si no hay conexion activa, retorna ok.
# Imprime JSON con el resultado.
#
# USO (sourced):
# source nordvpn_disconnect.sh
# nordvpn_disconnect
nordvpn_disconnect() {
if ! command -v nordvpn &>/dev/null; then
echo '{"ok":false,"error":"nordvpn no instalado"}' >&2
return 1
fi
local output
output=$(nordvpn disconnect 2>&1)
local rc=$?
if [ $rc -eq 0 ] || echo "$output" | grep -qi "not connected\|disconnected"; then
echo '{"ok":true,"status":"disconnected"}'
else
echo "{\"ok\":false,\"error\":$(echo "$output" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip()))' 2>/dev/null || echo "\"$output\"")}"
return 1
fi
}
+39
View File
@@ -0,0 +1,39 @@
---
name: nordvpn_get_ip
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "nordvpn_get_ip() -> json"
description: "Obtiene IP publica actual con fallback entre multiples servicios. Indica si la conexion VPN esta activa y el servidor usado."
tags: [vpn, nordvpn, ip, infra, network, verification]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/nordvpn_get_ip.sh"
---
## Ejemplo
```bash
source nordvpn_get_ip.sh
# Con VPN activa:
nordvpn_get_ip
# {"ok":true,"ip":"185.x.x.x","vpn_connected":true,"vpn_server":"es42.nordvpn.com","source":"https://api.ipify.org"}
# Sin VPN:
nordvpn_get_ip
# {"ok":true,"ip":"88.x.x.x","vpn_connected":false,"vpn_server":"","source":"https://api.ipify.org"}
```
## Notas
Usa ipify.org como servicio primario con fallback a ifconfig.me e icanhazip.com. Timeout de 5 segundos por servicio. Util para verificar que el tunel VPN esta activo antes de ejecutar operaciones sensibles a la IP.
+42
View File
@@ -0,0 +1,42 @@
# nordvpn_get_ip
# --------------
# Obtiene la IP publica actual para verificar que el tunel VPN funciona.
# Usa multiples servicios como fallback.
#
# USO (sourced):
# source nordvpn_get_ip.sh
# nordvpn_get_ip
nordvpn_get_ip() {
local ip=""
local source=""
# Intentar multiples servicios
for svc in "https://api.ipify.org" "https://ifconfig.me" "https://icanhazip.com"; do
ip=$(curl -s --max-time 5 "$svc" 2>/dev/null)
if echo "$ip" | grep -qP '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'; then
source="$svc"
break
fi
ip=""
done
if [ -z "$ip" ]; then
echo '{"ok":false,"error":"no se pudo obtener IP publica"}' >&2
return 1
fi
# Si nordvpn esta disponible, incluir info de conexion
local connected="false"
local vpn_server=""
if command -v nordvpn &>/dev/null; then
local status_output
status_output=$(nordvpn status 2>/dev/null)
if echo "$status_output" | grep -qi "connected"; then
connected="true"
vpn_server=$(echo "$status_output" | grep -iP "hostname|server" | head -1 | sed 's/.*: *//')
fi
fi
echo "{\"ok\":true,\"ip\":\"$ip\",\"vpn_connected\":$connected,\"vpn_server\":\"$vpn_server\",\"source\":\"$source\"}"
}
@@ -0,0 +1,37 @@
---
name: nordvpn_list_cities
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "nordvpn_list_cities(country: string) -> json"
description: "Lista ciudades disponibles de un pais en NordVPN como array JSON ordenado."
tags: [vpn, nordvpn, cities, infra, network]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/nordvpn_list_cities.sh"
---
## Ejemplo
```bash
source nordvpn_list_cities.sh
nordvpn_list_cities Spain
# {"ok":true,"country":"Spain","count":2,"cities":["Barcelona","Madrid"]}
nordvpn_list_cities "United_States"
# {"ok":true,"country":"United_States","count":15,"cities":["Atlanta","Buffalo",...]}
```
## Notas
El nombre de pais debe coincidir con lo que devuelve `nordvpn countries`. Usa underscores para paises compuestos (ej: United_States). Las ciudades se devuelven con espacios.
@@ -0,0 +1,38 @@
# nordvpn_list_cities
# -------------------
# Lista las ciudades disponibles de un pais en NordVPN como array JSON.
#
# USO (sourced):
# source nordvpn_list_cities.sh
# nordvpn_list_cities Spain
nordvpn_list_cities() {
local country="${1:?nordvpn_list_cities: se requiere pais como argumento}"
if ! command -v nordvpn &>/dev/null; then
echo '{"ok":false,"error":"nordvpn no instalado"}' >&2
return 1
fi
local output
output=$(nordvpn cities "$country" 2>&1)
local rc=$?
if [ $rc -ne 0 ]; then
echo "{\"ok\":false,\"error\":$(echo "$output" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip()))' 2>/dev/null || echo "\"$output\"")}"
return 1
fi
echo "$output" | python3 -c '
import sys, json, re
country = "'"$country"'"
text = sys.stdin.read()
text = re.sub(r"\x1b\[[0-9;]*m", "", text)
text = re.sub(r"[\t\r]", " ", text)
cities = [c.strip().replace("_", " ") for c in re.split(r"[,\n]+", text) if c.strip() and c.strip() != "-"]
cities = [c for c in cities if len(c) > 1]
cities.sort()
print(json.dumps({"ok": True, "country": country, "count": len(cities), "cities": cities}))
'
}
@@ -0,0 +1,34 @@
---
name: nordvpn_list_countries
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "nordvpn_list_countries() -> json"
description: "Lista paises disponibles en NordVPN como array JSON ordenado alfabeticamente."
tags: [vpn, nordvpn, countries, infra, network]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/nordvpn_list_countries.sh"
---
## Ejemplo
```bash
source nordvpn_list_countries.sh
nordvpn_list_countries
# {"ok":true,"count":60,"countries":["Albania","Argentina","Australia",...,"United States","Vietnam"]}
```
## Notas
Parsea la salida de `nordvpn countries` eliminando codigos ANSI y normalizando separadores. Los nombres de paises se devuelven con espacios en vez de underscores.
@@ -0,0 +1,36 @@
# nordvpn_list_countries
# ----------------------
# Lista los paises disponibles en NordVPN como array JSON.
#
# USO (sourced):
# source nordvpn_list_countries.sh
# nordvpn_list_countries
nordvpn_list_countries() {
if ! command -v nordvpn &>/dev/null; then
echo '{"ok":false,"error":"nordvpn no instalado"}' >&2
return 1
fi
local output
output=$(nordvpn countries 2>&1)
local rc=$?
if [ $rc -ne 0 ]; then
echo "{\"ok\":false,\"error\":$(echo "$output" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip()))' 2>/dev/null || echo "\"$output\"")}"
return 1
fi
echo "$output" | python3 -c '
import sys, json, re
text = sys.stdin.read()
text = re.sub(r"\x1b\[[0-9;]*m", "", text)
text = re.sub(r"[\t\r]", " ", text)
# Split by comma, whitespace, or newline and clean
countries = [c.strip().replace("_", " ") for c in re.split(r"[,\n]+", text) if c.strip() and c.strip() != "-"]
countries = [c for c in countries if len(c) > 1]
countries.sort()
print(json.dumps({"ok": True, "count": len(countries), "countries": countries}))
'
}
@@ -0,0 +1,37 @@
---
name: nordvpn_set_protocol
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "nordvpn_set_protocol(protocol: string) -> json"
description: "Cambia el protocolo de NordVPN entre NordLynx (WireGuard) y OpenVPN. NordLynx recomendado por velocidad."
tags: [vpn, nordvpn, protocol, nordlynx, wireguard, openvpn, infra]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/nordvpn_set_protocol.sh"
---
## Ejemplo
```bash
source nordvpn_set_protocol.sh
nordvpn_set_protocol NordLynx
# {"ok":true,"protocol":"NordLynx"}
nordvpn_set_protocol OpenVPN
# {"ok":true,"protocol":"OpenVPN"}
```
## Notas
NordLynx es WireGuard wrapeado por NordVPN — mas rapido y moderno. OpenVPN es mas compatible con redes restrictivas. El cambio de protocolo requiere reconectar si hay una conexion activa.
@@ -0,0 +1,38 @@
# nordvpn_set_protocol
# --------------------
# Cambia el protocolo de NordVPN (NordLynx o OpenVPN).
# NordLynx = WireGuard (recomendado por velocidad).
#
# USO (sourced):
# source nordvpn_set_protocol.sh
# nordvpn_set_protocol NordLynx
# nordvpn_set_protocol OpenVPN
nordvpn_set_protocol() {
local protocol="${1:?nordvpn_set_protocol: se requiere protocolo (NordLynx|OpenVPN)}"
if ! command -v nordvpn &>/dev/null; then
echo '{"ok":false,"error":"nordvpn no instalado"}' >&2
return 1
fi
case "$protocol" in
NordLynx|nordlynx|NORDLYNX) protocol="NordLynx" ;;
OpenVPN|openvpn|OPENVPN) protocol="OpenVPN" ;;
*)
echo "{\"ok\":false,\"error\":\"protocolo invalido: $protocol (usar NordLynx o OpenVPN)\"}"
return 1
;;
esac
local output
output=$(nordvpn set protocol "$protocol" 2>&1)
local rc=$?
if [ $rc -eq 0 ] || echo "$output" | grep -qi "already set\|successfully"; then
echo "{\"ok\":true,\"protocol\":\"$protocol\"}"
else
echo "{\"ok\":false,\"error\":$(echo "$output" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip()))' 2>/dev/null || echo "\"$output\"")}"
return 1
fi
}
+37
View File
@@ -0,0 +1,37 @@
---
name: nordvpn_status
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "nordvpn_status() -> json"
description: "Obtiene estado actual de NordVPN como JSON estructurado. Incluye servidor, IP, pais, protocolo y estado de conexion."
tags: [vpn, nordvpn, status, infra, network]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/nordvpn_status.sh"
---
## Ejemplo
```bash
source nordvpn_status.sh
nordvpn_status
# {"ok":true,"connected":true,"status":"Connected","hostname":"es42.nordvpn.com","ip":"185.x.x.x","country":"Spain","city":"Madrid","current_technology":"NordLynx","current_protocol":"nordlynx","transfer":"1.2 MiB received, 500 KiB sent","uptime":"5 minutes 32 seconds"}
# Desconectado:
# {"ok":true,"connected":false,"status":"Disconnected"}
```
## Notas
Parsea la salida clave-valor de `nordvpn status` eliminando codigos ANSI. Los campos disponibles dependen del estado de conexion — cuando esta desconectado solo devuelve status y connected.
+43
View File
@@ -0,0 +1,43 @@
# nordvpn_status
# --------------
# Obtiene el estado actual de NordVPN como JSON estructurado.
# Parsea la salida clave-valor de `nordvpn status` a campos JSON.
#
# USO (sourced):
# source nordvpn_status.sh
# nordvpn_status
nordvpn_status() {
if ! command -v nordvpn &>/dev/null; then
echo '{"ok":false,"error":"nordvpn no instalado"}' >&2
return 1
fi
local output
output=$(nordvpn status 2>&1)
local rc=$?
if [ $rc -ne 0 ]; then
echo "{\"ok\":false,\"error\":$(echo "$output" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip()))' 2>/dev/null || echo "\"$output\"")}"
return 1
fi
# Parsear output clave: valor a JSON con python3
echo "$output" | python3 -c '
import sys, json, re
lines = sys.stdin.read().strip().split("\n")
data = {"ok": True}
for line in lines:
line = re.sub(r"\x1b\[[0-9;]*m", "", line).strip()
line = line.lstrip("- ")
if ":" in line:
key, _, val = line.partition(":")
key = key.strip().lower().replace(" ", "_")
val = val.strip()
if key == "status":
data["connected"] = val.lower() == "connected"
data[key] = val
print(json.dumps(data))
'
}
+44
View File
@@ -0,0 +1,44 @@
package infra
import (
"fmt"
)
// NordVPNContainerRunOpts opciones para ejecutar un container a traves del gateway NordVPN.
type NordVPNContainerRunOpts struct {
Image string // Imagen Docker a ejecutar (obligatorio)
Cmd []string // Comando a ejecutar en el container
Env map[string]string // Variables de entorno
Volumes []string // Bind mounts
Name string // Nombre del container (opcional)
Gateway string // Nombre del container NordVPN gateway (default: "nordvpn")
Detach bool // Ejecutar en background
Remove bool // Eliminar al terminar (--rm)
}
// NordVPNContainerRun ejecuta un container Docker cuyo trafico de red
// pasa por el container gateway NordVPN usando --network=container:<gateway>.
// Devuelve el ID del container creado.
func NordVPNContainerRun(opts NordVPNContainerRunOpts) (string, error) {
if opts.Image == "" {
return "", fmt.Errorf("image required")
}
if opts.Gateway == "" {
opts.Gateway = "nordvpn"
}
id, err := DockerRunContainer(opts.Image, DockerRunOpts{
Name: opts.Name,
Env: opts.Env,
Volumes: opts.Volumes,
Detach: opts.Detach,
Remove: opts.Remove,
Network: "container:" + opts.Gateway,
Cmd: opts.Cmd,
})
if err != nil {
return "", fmt.Errorf("nordvpn container run %s: %w", opts.Image, err)
}
return id, nil
}
+53
View File
@@ -0,0 +1,53 @@
---
name: nordvpn_container_run
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func NordVPNContainerRun(opts NordVPNContainerRunOpts) (string, error)"
description: "Ejecuta un container Docker cuyo trafico pasa por el gateway NordVPN usando --network=container:<gateway>. El container hereda la IP y tunel VPN del gateway."
tags: [vpn, nordvpn, docker, container, run, infra, network]
uses_functions: ["docker_run_container_go_infra"]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/nordvpn_container_run.go"
---
## Ejemplo
```go
// Verificar IP desde VPN
id, err := NordVPNContainerRun(NordVPNContainerRunOpts{
Image: "curlimages/curl",
Cmd: []string{"https://api.ipify.org"},
Remove: true,
Gateway: "nordvpn",
})
// Ejecutar scraper bajo VPN
id, err := NordVPNContainerRun(NordVPNContainerRunOpts{
Image: "my-scraper:latest",
Env: map[string]string{"TARGET_URL": "https://example.com"},
Volumes: []string{"/tmp/output:/output"},
Detach: true,
Name: "scraper-vpn",
})
// Navegador headless bajo VPN
id, err := NordVPNContainerRun(NordVPNContainerRunOpts{
Image: "chromedp/headless-shell",
Detach: true,
Name: "chrome-vpn",
})
```
## Notas
Requiere que el container gateway NordVPN este corriendo (usar `NordVPNContainerStart` primero). El container cliente no necesita capabilities especiales — hereda la red del gateway. Con `--network=container:X` el container no puede exponer puertos propios; los puertos deben mapearse en el gateway.
@@ -0,0 +1,73 @@
package infra
import (
"fmt"
"strings"
"time"
)
// NordVPNContainerOpts opciones para el container gateway NordVPN.
type NordVPNContainerOpts struct {
Token string // Token de acceso NordVPN (obligatorio)
Country string // Pais al que conectar (opcional, ej: "Spain")
City string // Ciudad (opcional, ej: "Madrid")
Protocol string // "NordLynx" o "OpenVPN" (default: NordLynx)
Name string // Nombre del container (default: "nordvpn")
}
// NordVPNContainerStart levanta un container Docker con NordVPN como gateway.
// Otros containers pueden usar su red con --network=container:<name>.
// Espera hasta que el tunel este activo o timeout de 30s.
func NordVPNContainerStart(opts NordVPNContainerOpts) (string, error) {
if opts.Token == "" {
return "", fmt.Errorf("nordvpn token required")
}
if opts.Name == "" {
opts.Name = "nordvpn"
}
if opts.Protocol == "" {
opts.Protocol = "NordLynx"
}
env := map[string]string{
"TOKEN": opts.Token,
"TECHNOLOGY": opts.Protocol,
}
if opts.Country != "" {
connect := opts.Country
if opts.City != "" {
connect += " " + opts.City
}
env["CONNECT"] = connect
}
// Limpiar container previo con el mismo nombre si existe
_ = DockerRemoveContainer(opts.Name, true)
id, err := DockerRunContainer("ghcr.io/bubuntux/nordvpn", DockerRunOpts{
Name: opts.Name,
Env: env,
Detach: true,
CapAdd: []string{"NET_ADMIN", "NET_RAW"},
})
if err != nil {
return "", fmt.Errorf("nordvpn container start: %w", err)
}
// Esperar a que el tunel este activo
for i := 0; i < 30; i++ {
time.Sleep(1 * time.Second)
logs, logErr := DockerContainerLogs(opts.Name, 20)
if logErr != nil {
continue
}
if strings.Contains(logs, "Connected") || strings.Contains(logs, "connected") {
return id, nil
}
if strings.Contains(logs, "error") || strings.Contains(logs, "failed") {
return id, fmt.Errorf("nordvpn connection failed, check logs: docker logs %s", opts.Name)
}
}
return id, fmt.Errorf("nordvpn connection timeout after 30s, check logs: docker logs %s", opts.Name)
}
@@ -0,0 +1,42 @@
---
name: nordvpn_container_start
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func NordVPNContainerStart(opts NordVPNContainerOpts) (string, error)"
description: "Levanta un container Docker con NordVPN como gateway de red. Otros containers pueden rutear su trafico a traves de este con --network=container:<name>. Espera hasta 30s a que el tunel este activo."
tags: [vpn, nordvpn, docker, container, gateway, infra, network]
uses_functions: ["docker_run_container_go_infra", "docker_remove_container_go_infra", "docker_container_logs_go_infra"]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt, strings, time]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/nordvpn_container_start.go"
---
## Ejemplo
```go
id, err := NordVPNContainerStart(NordVPNContainerOpts{
Token: os.Getenv("NORDVPN_TOKEN"),
Country: "Spain",
City: "Madrid",
})
if err != nil {
log.Fatal(err)
}
fmt.Println("VPN gateway:", id)
// Ahora otros containers pueden usar la VPN:
// docker run --network=container:nordvpn curlimages/curl https://api.ipify.org
```
## Notas
Usa la imagen `ghcr.io/bubuntux/nordvpn`. Requiere un token de acceso NordVPN (obtener con `nordvpn token` desde CLI o desde la web de NordVPN). Limpia containers previos con el mismo nombre automaticamente. El protocolo por defecto es NordLynx (WireGuard).
+29
View File
@@ -0,0 +1,29 @@
package infra
import (
"fmt"
)
// NordVPNContainerStop detiene y elimina el container gateway NordVPN.
// Tambien detiene containers que usen su red si se proporcionan.
func NordVPNContainerStop(gateway string, clientNames ...string) error {
if gateway == "" {
gateway = "nordvpn"
}
// Primero parar los clientes que usan la red del gateway
for _, name := range clientNames {
_ = DockerStopContainer(name, 5)
_ = DockerRemoveContainer(name, true)
}
// Parar y eliminar el gateway
if err := DockerStopContainer(gateway, 10); err != nil {
return fmt.Errorf("nordvpn container stop: %w", err)
}
if err := DockerRemoveContainer(gateway, true); err != nil {
return fmt.Errorf("nordvpn container remove: %w", err)
}
return nil
}
+35
View File
@@ -0,0 +1,35 @@
---
name: nordvpn_container_stop
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func NordVPNContainerStop(gateway string, clientNames ...string) error"
description: "Detiene y elimina el container gateway NordVPN y opcionalmente los containers cliente que usan su red."
tags: [vpn, nordvpn, docker, container, stop, cleanup, infra]
uses_functions: ["docker_stop_container_go_infra", "docker_remove_container_go_infra"]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/nordvpn_container_stop.go"
---
## Ejemplo
```go
// Parar solo el gateway
err := NordVPNContainerStop("nordvpn")
// Parar gateway y clientes asociados
err := NordVPNContainerStop("nordvpn", "chrome-vpn", "scraper-vpn")
```
## Notas
Detiene primero los containers cliente (si se proporcionan) y luego el gateway. Importante: si los clientes usan `--network=container:nordvpn`, deben pararse antes que el gateway para evitar errores de red. Los clientes se paran con timeout de 5s, el gateway con 10s.
+68
View File
@@ -0,0 +1,68 @@
package infra
import (
"regexp"
"strings"
)
// NordVPNStatus representa el estado parseado de nordvpn status.
type NordVPNStatus struct {
Connected bool // true si hay conexion activa
Status string // "Connected" o "Disconnected"
Hostname string // ej: "es42.nordvpn.com"
IP string // IP del servidor VPN
Country string // ej: "Spain"
City string // ej: "Madrid"
Technology string // ej: "NordLynx"
Protocol string // ej: "nordlynx"
Transfer string // ej: "1.2 MiB received, 500 KiB sent"
Uptime string // ej: "5 minutes 32 seconds"
}
// ansiRegexp elimina codigos de escape ANSI.
var ansiRegexp = regexp.MustCompile(`\x1b\[[0-9;]*m`)
// ParseNordVPNStatus parsea la salida de texto de `nordvpn status`
// a un struct tipado. Funcion pura — no ejecuta comandos.
func ParseNordVPNStatus(output string) NordVPNStatus {
var s NordVPNStatus
lines := strings.Split(output, "\n")
for _, line := range lines {
line = ansiRegexp.ReplaceAllString(line, "")
line = strings.TrimSpace(line)
line = strings.TrimLeft(line, "- ")
idx := strings.Index(line, ":")
if idx < 0 {
continue
}
key := strings.ToLower(strings.TrimSpace(line[:idx]))
val := strings.TrimSpace(line[idx+1:])
switch key {
case "status":
s.Status = val
s.Connected = strings.EqualFold(val, "connected")
case "hostname", "server":
s.Hostname = val
case "ip":
s.IP = val
case "country":
s.Country = val
case "city":
s.City = val
case "current technology":
s.Technology = val
case "current protocol":
s.Protocol = val
case "transfer":
s.Transfer = val
case "uptime":
s.Uptime = val
}
}
return s
}
+41
View File
@@ -0,0 +1,41 @@
---
name: parse_nordvpn_status
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: pure
signature: "func ParseNordVPNStatus(output string) NordVPNStatus"
description: "Parsea la salida de texto de nordvpn status a un struct tipado. Elimina codigos ANSI y normaliza claves."
tags: [vpn, nordvpn, parser, pure, infra]
uses_functions: []
uses_types: ["NordVPNStatus_go_infra"]
returns: []
returns_optional: false
error_type: ""
imports: ["regexp", "strings"]
tested: true
tests: ["TestParseNordVPNStatus_Connected", "TestParseNordVPNStatus_Disconnected", "TestParseNordVPNStatus_WithANSI"]
test_file_path: "functions/infra/parse_nordvpn_status_test.go"
file_path: "functions/infra/parse_nordvpn_status.go"
---
## Ejemplo
```go
output := `Status: Connected
Hostname: es42.nordvpn.com
IP: 185.230.124.42
Country: Spain
City: Madrid
Current Technology: NordLynx`
s := ParseNordVPNStatus(output)
// s.Connected == true
// s.Hostname == "es42.nordvpn.com"
// s.Country == "Spain"
```
## Notas
Funcion pura — no ejecuta comandos ni accede a red. Maneja codigos ANSI que NordVPN CLI emite en terminal. Campos no presentes en la salida quedan como zero value.
@@ -0,0 +1,62 @@
package infra
import "testing"
func TestParseNordVPNStatus_Connected(t *testing.T) {
input := `Status: Connected
Hostname: es42.nordvpn.com
IP: 185.230.124.42
Country: Spain
City: Madrid
Current Technology: NordLynx
Current Protocol: nordlynx
Transfer: 1.2 MiB received, 500 KiB sent
Uptime: 5 minutes 32 seconds`
got := ParseNordVPNStatus(input)
if !got.Connected {
t.Error("expected Connected=true")
}
if got.Hostname != "es42.nordvpn.com" {
t.Errorf("Hostname = %q, want es42.nordvpn.com", got.Hostname)
}
if got.IP != "185.230.124.42" {
t.Errorf("IP = %q, want 185.230.124.42", got.IP)
}
if got.Country != "Spain" {
t.Errorf("Country = %q, want Spain", got.Country)
}
if got.City != "Madrid" {
t.Errorf("City = %q, want Madrid", got.City)
}
if got.Technology != "NordLynx" {
t.Errorf("Technology = %q, want NordLynx", got.Technology)
}
}
func TestParseNordVPNStatus_Disconnected(t *testing.T) {
input := `Status: Disconnected`
got := ParseNordVPNStatus(input)
if got.Connected {
t.Error("expected Connected=false")
}
if got.Status != "Disconnected" {
t.Errorf("Status = %q, want Disconnected", got.Status)
}
}
func TestParseNordVPNStatus_WithANSI(t *testing.T) {
input := "\x1b[32mStatus: Connected\x1b[0m\n\x1b[32m- Hostname: us1234.nordvpn.com\x1b[0m"
got := ParseNordVPNStatus(input)
if !got.Connected {
t.Error("expected Connected=true with ANSI codes")
}
if got.Hostname != "us1234.nordvpn.com" {
t.Errorf("Hostname = %q, want us1234.nordvpn.com", got.Hostname)
}
}
+31
View File
@@ -0,0 +1,31 @@
---
name: NordVPNStatus
lang: go
domain: infra
version: "1.0.0"
algebraic: product
definition: "type NordVPNStatus struct { Connected bool; Status string; Hostname string; IP string; Country string; City string; Technology string; Protocol string; Transfer string; Uptime string }"
description: "Estado parseado de nordvpn status. Contiene informacion de conexion, servidor, ubicacion y protocolo."
tags: [vpn, nordvpn, status, infra]
uses_types: []
file_path: "functions/infra/parse_nordvpn_status.go"
---
## Ejemplos
```go
s := NordVPNStatus{
Connected: true,
Status: "Connected",
Hostname: "es42.nordvpn.com",
IP: "185.230.124.42",
Country: "Spain",
City: "Madrid",
Technology: "NordLynx",
Protocol: "nordlynx",
}
```
## Notas
Producido por ParseNordVPNStatus. Los campos corresponden a las claves de la salida de `nordvpn status`. Transfer y Uptime son strings sin parsear — incluir parseo numerico si se necesita.