From 1d2b6dec354c1656c3dd901c98011783ae5c335d Mon Sep 17 00:00:00 2001 From: Brian Wiborg Date: Thu, 19 Jun 2025 23:42:43 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20PoC=20quick-share=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This addresses #12. Usability needs improvement. --- README.md | 7 ++++++- cmd/ziina/main.go | 42 +++++++++++++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5b4bb12..42517ce 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ Ziina is (basically) server-less. You only need a standard OpenSSH server with a public IP that serves as an entry-point for your peers. Your peers only need a standard OpenSSH client. +Starting Ziina inside an already open Zellij session will invite peers into that session. +Otherwise Ziina will spin up a new Zellij session with a random name. + ## How does it work? Ziina configures an ephemeral SSH remote port-forwarding tunnel on your public SSH server, pointing back to a local high-port. @@ -69,7 +72,7 @@ By default, Ziina will bind the builtin SSH server to `127.0.0.1:2222`. If you explicitly decide to bind it to `:2222`, you can make your Zellij session available on your LAN. Peers in your network can then connect to the high-port on your Zellij host, directly, effectively bypassing the round-trip through the tunnel. -If you don't provide an SSH host-key, Ziina will generate a random one on every start. +If you don't provide an SSH host-key, Ziina will generate a random in-memory key, every time it starts. ## Installation @@ -91,6 +94,8 @@ Your peers: go install github.com/ziinaio/ziina@latest ``` +Or you can download the [latest release build](https://github.com/ziinaio/ziina/releases) for your platform. + ## Usage ``` diff --git a/cmd/ziina/main.go b/cmd/ziina/main.go index 89e327d..d97376c 100644 --- a/cmd/ziina/main.go +++ b/cmd/ziina/main.go @@ -59,6 +59,12 @@ var ( // roUser contains the username for read-only access roUser = "" + + // quickShareMode flags if Ziina is running in quick-share mode + quickShareMode = false + + // xdgRuntimeDir is needed for Zellij session discovery + xdgRuntimeDir = "" ) // charset contains the list of available characters for random session-name generation. @@ -108,6 +114,9 @@ var App = &cli.App{ }, }, Action: func(ctx *cli.Context) error { + // we need this for Zellij session discovery + xdgRuntimeDir = os.Getenv("XDG_RUNTIME_DIR") + // Separate out the port from the listen-address. parts := strings.Split(ctx.String("listen"), ":") if len(parts) != 2 { @@ -135,10 +144,23 @@ var App = &cli.App{ username = currentUser.Username } - // Generate a random Zellij session-name. - sessionName, err = randomString(7) - if err != nil { - return err + // Check ZELLIJ_SESSION_NAME env-var + zellijSessionName := os.Getenv("ZELLIJ_SESSION_NAME") + if zellijSessionName != "" { + fmt.Println("") + log.Println("Starting from within Zellij session: ", zellijSessionName) + quickShareMode = true + } + + if quickShareMode { + sessionName = zellijSessionName + } else { + // Generate a random Zellij session-name. + sessionName, err = randomString(7) + if err != nil { + return err + } + sessionName = fmt.Sprintf("ziina-%s", sessionName) } // Generate a random username for full read-write access. @@ -197,14 +219,19 @@ var App = &cli.App{ fmt.Printf(" ssh -p %d %s@%s # read-write\n", port, rwUser, displayHost) fmt.Printf(" ssh -p %d %s@%s # read-only\n", port, roUser, displayHost) } - fmt.Println("\nPress Enter to continue...") - bufio.NewReader(os.Stdin).ReadBytes('\n') // Start the Zellij session over SSH if server == "" { serverOrHost = listenHost } - return runZellij(serverOrHost, sessionName, port) + + if quickShareMode { + select {} // just keep running + } else { + fmt.Println("\nPress Enter to continue...") + bufio.NewReader(os.Stdin).ReadBytes('\n') + return runZellij(serverOrHost, sessionName, port) + } }, } @@ -237,6 +264,7 @@ func runServer(chGuard chan struct{}, port int, listenAddr, hostKeyFile, entrypo // Set TERM environment variable cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", ptyReq.Term)) cmd.Env = append(cmd.Env, fmt.Sprintf("SHELL=%s", os.Getenv("SHELL"))) + cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", xdgRuntimeDir)) cmd.Env = append(cmd.Env, fmt.Sprintf("ZIINA_CONNECTION_INFO=%s", fmt.Sprintf("ssh -p %d %s@%s", port, rwUser, entrypoint))) cmd.Env = append(cmd.Env, fmt.Sprintf("ZIINA_CONNECTION_INFO_RO=%s", fmt.Sprintf("ssh -p %d %s@%s", port, roUser, entrypoint))) From e36e1eb737181b7c19e632b93c518d470ae2652f Mon Sep 17 00:00:00 2001 From: Brian Wiborg Date: Fri, 20 Jun 2025 00:06:38 +0200 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=9A=9A=20Rebrand=20to=20zmate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 83 ++++++++++++++++++++++++++++------------------- cmd/ziina/main.go | 34 +++++++++---------- 2 files changed, 66 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 42517ce..fda0435 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,45 @@ -# Ziina +# zmate 💻 📤 👥 Instant terminal sharing; using Zellij. [![asciicast](https://asciinema.org/a/35n5GUFE4PXc1DMdp3BJozRGT.svg)](https://asciinema.org/a/35n5GUFE4PXc1DMdp3BJozRGT) -Ziina lets you invite peers into a local [Zellij](https://github.com/zellij-org/zellij) session over untrusted networks, despite you being behind a NAT gateway. +zmate lets you invite peers into a local [Zellij](https://github.com/zellij-org/zellij) session over untrusted networks, despite you being behind a NAT gateway. It is heavily inspired by [tmate](https://github.com/tmate-io/tmate). -Ziina is (basically) server-less. +zmate is (basically) server-less. You only need a standard OpenSSH server with a public IP that serves as an entry-point for your peers. Your peers only need a standard OpenSSH client. -Starting Ziina inside an already open Zellij session will invite peers into that session. -Otherwise Ziina will spin up a new Zellij session with a random name. +Starting zmate inside an already open Zellij session will invite peers into that session. +Otherwise zmate will spin up a new Zellij session with a random name. ## How does it work? -Ziina configures an ephemeral SSH remote port-forwarding tunnel on your public SSH server, pointing back to a local high-port. +zmate configures an ephemeral SSH remote port-forwarding tunnel on your public SSH server, pointing back to a local high-port. It then starts a minimal SSH server on that local high-port, that throws connecting clients directly into a Zellij session. Peers connecting to the high-port on your server via SSH are forwarded through the tunnel directly into your local Zellij session. -Once the host terminates Ziina (by closing the Zellij session), the remote port-forwarding tunnel and internal SSH server are terminated and all peers automatically kicked. +Once the host terminates zmate (by closing the Zellij session), the remote port-forwarding tunnel and internal SSH server are terminated and all peers automatically kicked. > The host should always terminate the Zellij session by closing all tabs and panes. -> Simply detaching will still close Ziina and therefor terminate the builtin SSH server and the tunnel. +> Simply detaching will still close zmate and therefor terminate the builtin SSH server and the tunnel. > However, it will leave behind a dangling Zellij session and also likely screw up your peers' terminal, because their connection gets terminated very disgracefully. -### Ziina Session Life-Cycle +### zmate Session Life-Cycle ```mermaid sequenceDiagram participant Host - participant Ziina + participant zmate participant Zellij participant Builtin-SSHd participant Public-SSHd participant Peer - Note over Host,Peer: Start Ziina - Host->>Ziina: Start Ziina - Ziina->>Builtin-SSHd: Start builtin SSHd (2222) - Ziina->>Public-SSHd: Configure remote port-forwarding tunnel (2222) + Note over Host,Peer: Start zmate + Host->>zmate: Start zmate + zmate->>Builtin-SSHd: Start builtin SSHd (2222) + zmate->>Public-SSHd: Configure remote port-forwarding tunnel (2222) Public-SSHd-->>Builtin-SSHd: Forwards connections back to builtin SSHd (2222) Note over Host,Public-SSHd: Host connects to session @@ -53,26 +53,26 @@ sequenceDiagram Builtin-SSHd->>Zellij: Exec into Zellij session Note over Host,Peer: Zellij session closed or host detached - Ziina->>Public-SSHd: Terminate remote port-forwarding tunnel - Ziina->>Builtin-SSHd: Terminate builtin SSHd - Ziina->>Host: Terminate Ziina + zmate->>Public-SSHd: Terminate remote port-forwarding tunnel + zmate->>Builtin-SSHd: Terminate builtin SSHd + zmate->>Host: Terminate zmate ``` ## Security Model -Both, the remote port-forwarding and the builtin minimal SSH server, are initiated and terminated with Ziina. -While Ziina is not running, no listening-port will be bound, neither on your server, nor locally. -You can choose the port on which to bind when you start Ziina; default is 2222. +Both, the remote port-forwarding and the builtin minimal SSH server, are initiated and terminated with zmate. +While zmate is not running, no listening-port will be bound, neither on your server, nor locally. +You can choose the port on which to bind when you start zmate; default is 2222. The builtin minimal SSH server implements authentication and authorization solely via the username. Connecting peers must know the correct username. Peers connecting with a wrong username are immediately disconnected. -By default, Ziina will bind the builtin SSH server to `127.0.0.1:2222`. +By default, zmate will bind the builtin SSH server to `127.0.0.1:2222`. If you explicitly decide to bind it to `:2222`, you can make your Zellij session available on your LAN. Peers in your network can then connect to the high-port on your Zellij host, directly, effectively bypassing the round-trip through the tunnel. -If you don't provide an SSH host-key, Ziina will generate a random in-memory key, every time it starts. +If you don't provide an SSH host-key, zmate will generate a random in-memory key, every time it starts. ## Installation @@ -91,28 +91,43 @@ Your peers: ### Install via Go ``` -go install github.com/ziinaio/ziina@latest +go install github.com/ziinaio/zmate@latest ``` -Or you can download the [latest release build](https://github.com/ziinaio/ziina/releases) for your platform. +Or you can download the [latest release build](https://github.com/ziinaio/zmate/releases) for your platform. ## Usage ``` NAME: - ziina - 💻 📤 👥 Instant terminal sharing; using Zellij. - - ███████╗██╗██╗███╗ ██╗ █████╗ - ╚══███╔╝██║██║████╗ ██║██╔══██╗ - ███╔╝ ██║██║██╔██╗ ██║███████║ - ███╔╝ ██║██║██║╚██╗██║██╔══██║ - ███████╗██║██║██║ ╚████║██║ ██║ - ╚══════╝╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ - + zmate - 💻 📤 👥 Instant terminal sharing; using Zellij. + + ███████╗███╗ ███╗ █████╗ ████████╗███████╗ + ╚══███╔╝████╗ ████║██╔══██╗╚══██╔══╝██╔════╝ + ███╔╝ ██╔████╔██║███████║ ██║ █████╗ + ███╔╝ ██║╚██╔╝██║██╔══██║ ██║ ██╔══╝ + ███████╗██║ ╚═╝ ██║██║ ██║ ██║ ███████╗ + ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝ USAGE: ziina [global options] command [command options] +DESCRIPTION: + + Invite peers in you LAN. + + ziina -l 192.168.1.2:2222 + + Invite peers using **ssh.example.com** as entrypoint for your peers: + + ziina -s ssh.example.com + + Show connection info: + + echo $ZIINA_CONNECTION_INFO + echo $ZIINA_CONNECTION_INFO_RO + + COMMANDS: help, h Shows a list of commands or help for one command @@ -127,7 +142,7 @@ GLOBAL OPTIONS: ### Host ``` -ziina -s myserver +zmate -s myserver ``` This will generate a random 7 digit Zellij session-name. diff --git a/cmd/ziina/main.go b/cmd/ziina/main.go index d97376c..65fccca 100644 --- a/cmd/ziina/main.go +++ b/cmd/ziina/main.go @@ -1,4 +1,4 @@ -package ziina +package zmate import ( "bufio" @@ -26,27 +26,27 @@ import ( ) const banner = ` -███████╗██╗██╗███╗ ██╗ █████╗ -╚══███╔╝██║██║████╗ ██║██╔══██╗ - ███╔╝ ██║██║██╔██╗ ██║███████║ - ███╔╝ ██║██║██║╚██╗██║██╔══██║ -███████╗██║██║██║ ╚████║██║ ██║ -╚══════╝╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ +███████╗███╗ ███╗ █████╗ ████████╗███████╗ +╚══███╔╝████╗ ████║██╔══██╗╚══██╔══╝██╔════╝ + ███╔╝ ██╔████╔██║███████║ ██║ █████╗ + ███╔╝ ██║╚██╔╝██║██╔══██║ ██║ ██╔══╝ +███████╗██║ ╚═╝ ██║██║ ██║ ██║ ███████╗ +╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝ ` const examples = ` Invite peers in you LAN. - ziina -l 192.168.1.2:2222 + zmate -l 192.168.1.2:2222 Invite peers using **ssh.example.com** as entrypoint for your peers: - ziina -s ssh.example.com + zmate -s ssh.example.com Show connection info: - echo $ZIINA_CONNECTION_INFO - echo $ZIINA_CONNECTION_INFO_RO + echo $ZMATE_CONNECTION_INFO + echo $ZMATE_CONNECTION_INFO_RO ` var ( @@ -60,7 +60,7 @@ var ( // roUser contains the username for read-only access roUser = "" - // quickShareMode flags if Ziina is running in quick-share mode + // quickShareMode flags if zmate is running in quick-share mode quickShareMode = false // xdgRuntimeDir is needed for Zellij session discovery @@ -85,7 +85,7 @@ func randomString(length int) (string, error) { // App serves as entry-point for github.com/urfave/cli var App = &cli.App{ - Name: "ziina", + Name: "zmate", Usage: "💻 📤 👥 Instant terminal sharing; using Zellij." + "\n" + gorainbow.Rainbow(banner), Description: examples, Flags: []cli.Flag{ @@ -160,7 +160,7 @@ var App = &cli.App{ if err != nil { return err } - sessionName = fmt.Sprintf("ziina-%s", sessionName) + sessionName = fmt.Sprintf("zmate-%s", sessionName) } // Generate a random username for full read-write access. @@ -265,8 +265,8 @@ func runServer(chGuard chan struct{}, port int, listenAddr, hostKeyFile, entrypo cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", ptyReq.Term)) cmd.Env = append(cmd.Env, fmt.Sprintf("SHELL=%s", os.Getenv("SHELL"))) cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", xdgRuntimeDir)) - cmd.Env = append(cmd.Env, fmt.Sprintf("ZIINA_CONNECTION_INFO=%s", fmt.Sprintf("ssh -p %d %s@%s", port, rwUser, entrypoint))) - cmd.Env = append(cmd.Env, fmt.Sprintf("ZIINA_CONNECTION_INFO_RO=%s", fmt.Sprintf("ssh -p %d %s@%s", port, roUser, entrypoint))) + cmd.Env = append(cmd.Env, fmt.Sprintf("ZMATE_CONNECTION_INFO=%s", fmt.Sprintf("ssh -p %d %s@%s", port, rwUser, entrypoint))) + cmd.Env = append(cmd.Env, fmt.Sprintf("ZMATE_CONNECTION_INFO_RO=%s", fmt.Sprintf("ssh -p %d %s@%s", port, roUser, entrypoint))) // Start Zellij in a new PTY ptmx, err := pty.Start(cmd) @@ -313,7 +313,7 @@ func runServer(chGuard chan struct{}, port int, listenAddr, hostKeyFile, entrypo chGuard <- struct{}{} }() - log.Printf("Starting Ziina server on %s...\n", listenAddr) + log.Printf("Starting zmate server on %s...\n", listenAddr) return server.ListenAndServe() } From 26b1b009f58e72cbeda5f956408765b2ea65f9ec Mon Sep 17 00:00:00 2001 From: Brian Wiborg Date: Fri, 20 Jun 2025 00:19:18 +0200 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=9A=9A=20More=20rebranding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/{ziina => zmate}/main.go | 0 go.mod | 2 +- main.go | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename cmd/{ziina => zmate}/main.go (100%) diff --git a/cmd/ziina/main.go b/cmd/zmate/main.go similarity index 100% rename from cmd/ziina/main.go rename to cmd/zmate/main.go diff --git a/go.mod b/go.mod index 75618ff..3f3c5ad 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/ziinaio/ziina +module github.com/ziinaio/zmate go 1.24.2 diff --git a/main.go b/main.go index dc660aa..004b8b7 100644 --- a/main.go +++ b/main.go @@ -4,11 +4,11 @@ import ( "log" "os" - "github.com/ziinaio/ziina/cmd/ziina" + "github.com/ziinaio/zmate/cmd/zmate" ) func main() { - if err := ziina.App.Run(os.Args); err != nil { + if err := zmate.App.Run(os.Args); err != nil { log.Fatal(err) } }