Quickstart: msh

This guide installs msh — the Mezite client CLI — on a developer laptop and walks you through logging in, listing nodes, opening an SSH session, and copying a file with msh scp. By the end you will be SSHing into the node you brought online in the mezd quickstart without ever opening an inbound port on it.

It should take under five minutes once your hub is running and the node is online.

msh is the end-user CLI — it logs you in and opens SSH sessions. For admin tasks — creating users, roles, tokens, or certificate authorities — use mezctl instead.

Prerequisites

  • A managed hub from the Managed mezhub quickstart (you should have a hub hostname like acme.hub.mezite.com and the initial admin password)
  • At least one node online from the mezd quickstart — confirm it appears as online in mezctl nodes ls before continuing
  • A workstation running Linux (x86_64 or arm64) or macOS (x86_64 or arm64)
  • Outbound TCP/443 from your workstation to your hub host — the laptop dials the hub directly, the hub never dials back

Step 1: Install msh

Homebrew (recommended)

On macOS and on Linux hosts running Homebrew, this is the shortest path. It installs msh onlymezhub, mezd and mezctl are deliberately left off your PATH, which is what you want on a laptop:

Install msh with Homebrew bash
brew install leonardaustin/tap/msh

msh version

From the release archive

See Installation for the canonical download URLs and the signature checks. The archive is flat and carries all four binaries plus LICENSE and README.md, so name msh explicitly instead of untarring the whole thing into a directory on your PATH:

Install msh from the archive bash
# PLATFORM is one of linux-amd64, linux-arm64, darwin-amd64, darwin-arm64
tar -xzf "mezite-$PLATFORM.tar.gz" -C /tmp msh
sudo install -m 0755 /tmp/msh /usr/local/bin/msh

msh --help

The macOS binaries are ad-hoc signed but not notarized with Apple. Homebrew clears the quarantine attribute for you; a copy you downloaded through a browser will be quarantined and Gatekeeper will refuse to run it, so clear the attribute by hand:

macOS — clear quarantine bash
sudo xattr -d com.apple.quarantine /usr/local/bin/msh 2>/dev/null || true

msh --help

Step 2: Log In

Point msh at your hub on port 443 — the managed hub multiplexes the gRPC auth service and the SSH proxy over a single TLS port via ALPN, so there is only ever one address to remember. Use the same MEZITE_AUTH_SERVER shell variable convention from the Managed mezhub quickstart so your shell carries over:

Log in to the managed hub bash
export MEZITE_AUTH_SERVER=<your-hub-host>:443
export MEZITE_ADMIN_PASSWORD='<initial-admin-password>'

msh login \
  --proxy="$MEZITE_AUTH_SERVER" \
  --user=admin \
  --password="$MEZITE_ADMIN_PASSWORD"

A successful login writes a short-lived SSH certificate and the matching private key to ~/.mezite/; subsequent msh commands pick those up automatically. Confirm the session is healthy:

Check login status bash
msh status

# Output:
# Cluster:     acme.hub.mezite.com
# Proxy:       acme.hub.mezite.com:443
# User:        admin
# Roles:       admin
# Valid until: 2026-07-30 21:00:00 UTC (11h59m59s remaining)

Step 3: List Nodes

msh ls shows every node your certificate can reach. The output is five tab-aligned columns:

List reachable nodes bash
msh ls

# Output:
# HOSTNAME  ROLE  STATUS  LABELS             VERSION
# web-01    node  online  env=prod,role=web  0.2.24

If your node is missing from the list, check that mezd is still running on the host and that mezctl nodes ls reports it as online from the admin shell.


Step 4: SSH to a Node

The form is msh ssh [flags] <hostname> [-- command...]. Use --login to pick the remote Unix user — for the default admin role, root is allowed:

Open an interactive SSH session bash
msh ssh --login=root web-01

Want to run a one-shot command instead of dropping into a shell? Put it after --:

Run a single remote command bash
msh ssh --login=root web-01 -- whoami

# Output:
# root
The login user must be allowed by your Mezite role. The default admin role grants root and a handful of common system users out of the box. If you assign a non-admin user a role that does not include the login you ask for, the SSH handshake will fail with a clear permission error. See the RBAC Guide for adding logins to a role.

Step 5: Copy a File with msh scp

msh scp mirrors scp: msh scp <src> <dst>, with the remote side written as user@host:path. The round trip below uploads a file, downloads it back to a new path, and diffs the two copies to confirm bytes survived:

Upload, download, and diff bash
echo "hello from msh" > /tmp/msh-hello.txt

# Upload
msh scp /tmp/msh-hello.txt root@web-01:/tmp/msh-hello.txt

# Download back to a new path
msh scp root@web-01:/tmp/msh-hello.txt /tmp/msh-hello.roundtrip.txt

# Confirm the round-trip was byte-exact
diff /tmp/msh-hello.txt /tmp/msh-hello.roundtrip.txt && echo "round trip OK"

Use -r to recursively copy a directory. Both upload and download go through the same proxy tunnel msh ssh uses, so no extra ports or firewall rules are needed.


Step 6: Log Out

When you are done, drop your local certificates with msh logout. This removes the profile directory under ~/.mezite/ — you will need to msh login again to use the cluster.

Log out bash
msh logout

# Output:
# Logged out from <cluster-name>

What Just Happened?

  1. msh login authenticated against the hub's gRPC auth service over TLS (multiplexed by ALPN onto port 443), generated an SSH keypair locally, and received a short-lived SSH certificate signed by the hub's User CA. The private key and certificate live in ~/.mezite/ and expire automatically.
  2. msh ls asked the hub for the cluster's node list and printed every node back to you. RBAC is enforced at SSH-connect time — the proxy rejects connections to nodes your role does not allow, even if you can see them in the list.
  3. msh ssh opened a connection to the proxy, which forwarded the session through the reverse tunnel mezd is holding open from the node back to the hub. There is no inbound listener on the node.
  4. msh scp opened the same kind of tunnelled SSH connection and ran SFTP over it, so file transfers traverse the same audited, certificate-authenticated path as interactive sessions.

Next Steps

You now have a working end-to-end SSH path — admin login, node online, laptop logged in, SSH and SCP working. The next step is usually adding a real user with a non-admin role:

Create a non-admin user with SSH access bash
mezctl \
  --auth-server="$MEZITE_AUTH_SERVER" \
  --token="$MEZITE_TOKEN" \
  users create alice --roles=ssh-access --password='<initial-password>'
The built-in ssh-access role is deliberately narrow: it matches only nodes labelled env=staging or env=dev, denies anything labelled env=production, and allows the logins ubuntu and the user's own username. The node you enrolled in the mezd quickstart carries env=prod, so alice will not be able to reach it until you relabel the node or define a role that matches it. The RBAC Guide covers writing your own.
  • RBAC Guide — define which users can reach which nodes, and which Unix logins they are allowed to assume.
  • SSO Guide — replace --user/--password with OIDC, SAML, LDAP, or GitHub OAuth so users log in through your identity provider.
  • Session Recording — capture every interactive SSH session for audit and replay.
  • msh Reference — the full command surface, including hardware-key login, identity-file export, and session invitations.