Gestor de túneles SSH en Rust: CLI vs GUI nativa
En pocas palabras: Para un gestor de túneles SSH en Rust conviene CLI si priorizás velocidad de lanzamiento: se distribuye sin firma de código, mientras que la GUI nativa con Tauri requiere cuenta de Apple Developer y notarización en macOS, pero suma indicador visual de estado en bandeja.
Un desarrollador construyó el mismo gestor de túneles SSH dos veces en Rust: una vez como CLI con clap y TOML, y otra como GUI nativa con Tauri, para comparar en la práctica los costos de distribución, gestión de procesos e integración con el sistema operativo. Publicó el comparativo completo en dev.to el 17 de septiembre de 2026.
Un gestor de túneles SSH es una herramienta que automatiza la creación, el monitoreo y el cierre de túneles SSH (reenvío de puertos local o remoto) sin que tengas que memorizar los flags de ssh -N -L. Sirve para acceder a bases de datos internas, jump boxes y entornos de staging, y podés construirlo como CLI, como aplicación de escritorio, o como las dos cosas compartiendo el mismo núcleo. Lo interesante del caso no es el código en sí —eso lo resuelve cualquiera con un fin de semana libre— sino la pregunta que casi nadie se hace antes de arrancar: ¿para quién estoy construyendo esto?
En este artículo:
- En 30 segundos
- ¿Qué problema resuelve un gestor de túneles SSH?
- ¿Cómo maneja el proceso SSH un tunnel manager escrito en Rust?
- ¿Qué ventajas tiene construir la CLI con clap y TOML?
- ¿Qué ventajas ofrece la GUI nativa con Tauri?
- ¿Qué le cuesta a cada enfoque en la práctica?
- ¿Por qué la firma y notarización en macOS le suma tanta fricción a la GUI?
- Ejemplo hipotético: cómo se vería esta decisión en un equipo chico
- ¿Cuál es el trade-off real entre CLI y GUI para un gestor de túneles SSH?
- Criterios para decidir si te conviene CLI, GUI, o las dos
- Errores comunes al construir un gestor de túneles SSH propio
- Preguntas Frecuentes
- Conclusión
- Fuentes
En 30 segundos
- El autor construyó
tunlctl, un gestor de túneles SSH en Rust, dos veces: como CLI con clap/TOML y como GUI nativa con Tauri. - Ambas versiones comparten el mismo backend: un struct
Tunnelque shellea al binariosshdel sistema víastd::process::Command. - La CLI se distribuye con
cargo installo un binario estático víacross, sin firma ni notarización. - La GUI con Tauri necesita firma de código y notarización en macOS (vía
xcrun notarytool) para evitar las advertencias de Gatekeeper. - Conclusión del autor: la CLI gana en time-to-ship, la GUI gana en time-to-use, y terminó combinando ambas sobre el mismo core, igual que Docker con su CLI y Docker Desktop.
¿Qué problema resuelve un gestor de túneles SSH?
Resuelve el hecho de que nadie se acuerda la sintaxis de un comando como ssh -N -L 5432:db.internal:5432 -i ~/.ssh/jump_key jumpbox.example.com cuando tiene doce de esos abiertos en tres entornos distintos. Un gestor de túneles SSH te deja definir esas conexiones una vez y prenderlas o apagarlas sin escribir flags cada vez.
El disparador de este comparativo fue una app de macOS escrita en Swift que anda circulando: un ícono de barra de menú que levanta reenvíos de puertos locales y remotos sin tocar la terminal. La idea le pareció suficientemente buena como para construirla, pero sin quedar atado a macOS (spoiler: eso terminó costándole más de lo que pensaba en la versión gráfica).
Los casos de uso son siempre los mismos: jump boxes para llegar a servidores internos, acceso a bases de datos que no están expuestas a internet, y entornos de staging que cambian de host cada dos semanas. Si alguna vez perdiste la cuenta de qué túnel mataste el martes pasado, sabés exactamente de qué habla.
¿Cómo maneja el proceso SSH un tunnel manager escrito en Rust?
Shelleando al binario ssh del sistema operativo y manejándolo como subproceso, no con una librería SSH nativa. Rust no tiene ninguna que sea lo bastante madura para lidiar con las rarezas de autenticación por clave o por agente, así que tanto la versión CLI como la GUI terminan haciendo lo mismo que, según describe el propio autor en dev.to, hace la app de Swift por debajo.
El core es un struct Tunnel con tres métodos: start(), que arma el forward con Command::new("ssh") y los flags -N -L; stop(), que mata el proceso hijo y espera con wait(); y is_alive(), que chequea con try_wait() si el proceso sigue corriendo. Todo eso vive dentro de un HashMap que funciona como registro de túneles.
Este backend fue idéntico en esfuerzo para las dos versiones. Ahí no hay trade-off real: la divergencia arranca recién cuando tenés que decidir cómo un humano interactúa con ese registro. Y es justo ahí donde conviene detenerse antes de escribir una sola línea, porque esa decisión condiciona meses de mantenimiento después.
¿Qué ventajas tiene construir la CLI con clap y TOML?
Te ahorra toda la fricción de distribución y te da scripting gratis. La versión CLI, bautizada tunlctl, define los túneles en un archivo TOML ([tunnels.staging_db] con host, puerto local y puerto remoto) y se maneja con comandos tipo tunlctl up staging_db o tunlctl down staging_db.
- Distribución trivial: se instala con
cargo installo como binario estático compilado concrosspara Linux, macOS y Windows, sin firma de código ni cola de revisión de ninguna tienda. - Scriptability sin esfuerzo: apenas la mostró, le pidieron un flag
--json(tunlctl up staging --json) para meterla en otro tooling. Eso una GUI no lo resuelve tan fácil. - Persistencia con herramientas ya existentes: mantener los túneles vivos después de cerrar la CLI implica double-forking o generar unidades de
systemdolaunchdon demand, más plomería de la que una GUI necesita solo por quedarse abierta.
¿Qué ventajas ofrece la GUI nativa con Tauri?
Te da estado visible sin que tengas que preguntarle nada a la terminal. La versión GUI envuelve el mismo struct Tunnel detrás de comandos Tauri (como toggle_tunnel) y usa Rust más un frontend liviano en HTML/CSS para el menú de bandeja.
- Estado ambiental: un ícono de bandeja que se pone verde o rojo por túnel, actualizado por un poller en segundo plano, es mejor UX que tipear un comando de status. Es, según el autor, la razón principal por la que la app de Swift original le gustó a tanta gente.
- Duración del proceso gratis: la app misma es el proceso de larga duración; los túneles nacen y mueren con ella, sin necesidad de un daemon aparte.
- Integración nativa donde importa: por ejemplo, el keychain de macOS para guardar passphrases SSH, en vez de depender de que
ssh-agentesté bien configurado.
¿Qué le cuesta a cada enfoque en la práctica?
A la CLI le falta señal visual y a la GUI le sobra complejidad de build. Son los dos lados de la misma moneda, y no hay forma de tener ambos gratis: cada ventaja de un lado es, casi calcada, la desventaja del otro.
Con tunlctl no hay aviso de “este túnel se cayó”: o hacés polling de tunlctl status a mano, o te enterás cuando tu app empieza a tirar errores de conexión rechazada. No hay ícono en la barra de tareas ni click para togglear, y para una herramienta que se usa varias veces al día eso pesa.
La GUI paga otro precio. El bundle creció porque, aunque Tauri es bastante más liviano que Electron, seguís empaquetando un frontend basado en webview, lidiando con tauri.conf.json y depurando la serialización de IPC entre Rust y JS para cosas que en la CLI eran simples llamadas a función. Y el comportamiento de la bandeja del sistema no es parejo entre plataformas: en Linux depende de que el entorno de escritorio tenga una implementación funcional de StatusNotifierItem, algo más chico de lo que uno esperaría de una historia “multiplataforma”.
¿Por qué la firma y notarización en macOS le suma tanta fricción a la GUI?
Porque sin firmar, la app dispara advertencias de Gatekeeper en macOS y de SmartScreen en Windows, y firmarla no es un paso menor. Según la documentación oficial de Tauri sobre firma de código en macOS, hace falta una cuenta de Apple Developer (USD 99 por año en el plan pago) y un equipo Apple físico para generar el Certificate Signing Request.
Después viene crear el certificado (tipo Developer ID Application si vas a distribuir fuera de la App Store), instalarlo en el keychain o exportarlo como .p12 en base64 para CI/CD, y configurar variables de entorno como APPLE_ID, APPLE_PASSWORD, APPLE_CERTIFICATE y APPLE_TEAM_ID. Recién ahí podés notarizar con xcrun notarytool, un paso de CI que en el mundo CLI directamente no existe.
Ojo: Tauri tiene un atajo para pruebas. Se puede configurar una firma ad-hoc con la pseudo-identidad "-", útil en Apple Silicon donde el firmado es obligatorio para cualquier app bajada de internet. Pero eso no reemplaza la notarización si pensás distribuir la app por fuera de tu propia máquina, y confundir ambas cosas es de los errores más comunes cuando alguien firma por primera vez.
Ejemplo hipotético: cómo se vería esta decisión en un equipo chico
Este es un ejemplo hipotético, construido a partir de los trade-offs descritos en la fuente, no un caso real reportado por el autor.
Imaginemos un equipo de cuatro personas: dos backend que viven en la terminal y dos de soporte que casi no tocan una shell. Si ese equipo copia el enfoque del artículo al pie de la letra —CLI primero, GUI después—, el camino más barato sería este: la CLI sale primero, la usan los dos backend durante un par de semanas para validar que el registro TOML cubre los entornos reales (staging, producción, la VPN del cliente grande). Recién cuando alguien de soporte pide “¿no hay un botón para esto?”, ahí se justifica invertir en la GUI y en toda la fricción de firma que trae. Construir la GUI primero, sin haber probado el core en CLI, habría significado pagar el costo de notarización antes de saber si el modelo de datos (el TOML de túneles) estaba bien pensado.
La lección que se puede sacar de este ejemplo hipotético, y que coincide con lo que reporta el autor real, es que el orden importa tanto como la elección: probar el backend en su forma más barata de distribuir (CLI) antes de comprometerse con la infraestructura de firma de una GUI reduce el riesgo de notarizar una app cuyo modelo de datos todavía puede cambiar.
¿Cuál es el trade-off real entre CLI y GUI para un gestor de túneles SSH?
La CLI gana en tiempo de entrega y la GUI gana en tiempo de uso, así lo resume el propio autor. Subís la CLI, la probás en tu terminal, funciona en una tarde, la usás vos y tu equipo sin pedirle nada a nadie, y de repente alguien te pide una versión con ícono en la bandeja porque no quiere abrir la terminal para chequear un túnel, y ahí arranca toda la infraestructura de firma que la CLI nunca necesitó.
| Aspecto | CLI (tunlctl) | GUI (Tauri) |
|---|---|---|
| Distribución | cargo install o binario estático, sin firma | requiere firma y notarización en macOS; en Windows dispara advertencias de SmartScreen |
| Scripting | flag –json listo para automatizar | no aplica de forma nativa |
| Persistencia | unidades systemd/launchd generadas on demand | vive mientras la app esté abierta, sin daemon aparte |
| Estado visual | hay que hacer polling de tunlctl status | ícono de bandeja verde/rojo en tiempo real |
| Complejidad de build | baja, un crate | media/alta: IPC Rust-JS y webview |
| Tray en Linux | no aplica | depende de StatusNotifierItem del entorno |

La respuesta final del autor no fue elegir una: fue construir las dos sobre el mismo core, con la CLI como fuente de verdad y capa automatizable, y la GUI como una envoltura delgada y opcional para quien quiera el ícono de bandeja. No es una salida fácil, es la misma lógica por la que Docker tiene CLI y Docker Desktop al mismo tiempo.
Criterios para decidir si te conviene CLI, GUI, o las dos
Más allá de la anécdota puntual de tunlctl, los trade-offs que describe el autor se pueden traducir en preguntas concretas para cualquiera que esté por construir una herramienta interna parecida:
- ¿Quién la va a usar? Si el 100% del equipo ya vive en una terminal, invertir en firma de código y notarización para una GUI es gastar tiempo en un problema que nadie tiene.
- ¿Cuántas veces al día se toca la herramienta? Un uso ocasional tolera bien un comando de status manual. Un uso constante (decenas de veces por día) es donde el costo cognitivo de no tener señal visual empieza a pesar más que el costo de firmar una app.
- ¿Necesitás integrarla con otra automatización? Si la respuesta es sí —CI, scripts, otros binarios que consuman su output—, la CLI con un flag
--jsonresuelve eso de entrada; una GUI lo puede hacer, pero no es su fortaleza natural. - ¿Tenés ya infraestructura de firma para macOS? Si tu equipo ya firma y notariza otras apps (cuenta de Apple Developer activa, pipeline de CI con los certificados cargados), el costo marginal de sumar una GUI baja mucho. Si es la primera vez, contá ese setup como parte del esfuerzo real del proyecto, no como un detalle de último momento.
- ¿El equipo es multiplataforma de verdad? Si hay gente en Linux, conviene verificar de entrada si sus entornos de escritorio soportan bandeja del sistema antes de prometer una experiencia uniforme.
Errores comunes al construir un gestor de túneles SSH propio
- Buscar una librería SSH nativa “pura Rust” antes de aceptar la realidad: no existe una lo bastante madura para claves y agentes, así que terminás shelleando al binario ssh del sistema de todos modos. Mejor asumirlo desde el diseño inicial.
- Dejar la firma y notarización para el final: subestimar ese paso frena el release entero justo cuando ya tenés la GUI lista para mandar. Configurá el pipeline de CI con las variables de Apple antes de necesitarlo.
- Asumir que el tray funciona igual en todos lados: en Linux depende del entorno de escritorio y de si tiene StatusNotifierItem andando. No es un detalle menor para una historia “multiplataforma”.
- Duplicar la lógica de gestión de procesos entre CLI y GUI: si construís las dos interfaces, compartí el mismo crate de backend. Mantener dos structs Tunnel distintos es buscarse bugs de sincronización.
Algo que no está en la fuente pero conviene tener presente: si tu equipo administra servidores propios o instancias en un proveedor como donweb.com, estandarizar cómo cada persona accede a esos entornos (con un registro TOML compartido, por ejemplo) evita que la lista de túneles crezca sin control antes de que alguien decida construir una herramienta interna para ordenarla.
Preguntas Frecuentes
¿Qué es un SSH tunnel manager y para qué sirve?
Es una herramienta que administra túneles SSH (reenvíos de puertos local o remoto) sin que tengas que escribir el comando ssh completo cada vez. Sirve para acceder a bases de datos internas, servidores de staging o jump boxes desde tu máquina, prendiendo y apagando conexiones con un comando corto o un click en un ícono.
¿Conviene construir una herramienta interna como CLI o como GUI?
Depende de quién la use. Si el equipo ya vive en la terminal, la CLI se entrega más rápido y sin infraestructura de firma; si la herramienta va a manos de gente menos cómoda con la terminal, el ícono de bandeja de una GUI justifica el costo extra de firma y notarización. Como regla práctica: empezá por la CLI para validar el modelo de datos, y sumá la GUI recién cuando alguien la pida explícitamente.
¿Por qué hay que firmar y notarizar una app en macOS?
Porque sin firma, Gatekeeper marca la app como “dañada” o bloquea su apertura cuando se descarga del navegador. Firmar requiere una cuenta de Apple Developer y notarizar implica autenticar con Apple vía App Store Connect API o Apple ID y correr xcrun notarytool como parte del build.
¿Cómo se mantiene vivo un túnel SSH sin que se corte?
En la versión CLI, generando unidades de systemd o launchd on demand, o usando double-forking para que el túnel sobreviva al cierre de la terminal. En la versión GUI no hace falta nada de eso: el túnel vive mientras la app esté corriendo, porque el proceso principal la mantiene activa.
¿Qué diferencia hay entre Tauri y Electron para una app nativa?
Tauri es bastante más liviano que Electron en tamaño de bundle, según señala el autor del comparativo. Aun así, ambos siguen siendo frameworks basados en webview: seguís empaquetando un frontend web y lidiando con la comunicación entre esa capa y el código nativo, algo que una CLI pura no necesita.
Conclusión
Lo que cambia acá no es la lógica de manejar un túnel SSH, que es la misma línea de código en ambos casos. Lo que cambia es el costo de entregárselo a un humano: una CLI se manda en una tarde y no te pide nada más, una GUI te obliga a meterte en firma de código, notarización y comportamiento de bandeja que varía según el sistema operativo.
Si estás por construir una herramienta interna parecida, la pregunta que importa no es “CLI o GUI”, es quién la va a usar y cuántas veces al día. Si es para vos y tu equipo técnico, arrancá por la CLI y compartí el core cuando (y si) alguien te pida el ícono de bandeja. Es más barato agregar una GUI sobre un backend probado que mantener dos lógicas de gestión de procesos por separado, y mucho más barato que notarizar una app cuyo modelo de datos todavía está cambiando cada semana.






