A Browser Terminal on a ROS 2 Robot Without a VPN, and Why It Should Be Read-Only by Default
Agility Robotics unveiled Digit 5 today, and the part of the announcement that matters to anyone who runs robots for a living is not the 50 lb payload. It is the independent safety controller that "oversees Digit's response to the detection of people within an unsafe distance," and the Agility Arc fleet platform that shows operators uptime, throughput, and mean time between incidents. Fleet-scale humanoids get fleet-scale tooling. The ROS 2 robot on your bench gets whatever you can reach it with. This post is about getting a browser terminal on a ROS 2 robot with shell.online: one command on the robot, one link on your phone, no VPN, and a clear account of what that link does and does not expose.
The timing is not only about Digit. The ROS 2 release team shipped Humble Hawksbill Patch Release 15 yesterday, and the current LTS, Lyrical Luth, has been out since May with Ubuntu 26.04 as its Tier 1 platform. Plenty of fleets are mid-migration between the two, and somebody has to look at the actual terminal on the actual robot when a launch file behaves differently than it did in the sim.
Why ROS needs no adapter
shell.online transports terminal bytes. It does not know what ROS is, and it does not need to. The platforms page says it directly: source the ROS environment first, then wrap roscore, roslaunch, ros2 run, ros2 launch, colcon build, or a node with the normal shell command. No bridge node, no DDS discovery across a NAT. The PTY runs on the robot, and the browser sees what a person at the robot's console would see.
The one caveat, also from that page: ROS itself supports fewer OS and architecture pairs than the CLI does. Sharing a terminal does not make an unsupported ROS combination work.
Putting a browser terminal on a ROS 2 robot
Install the CLI on the robot's compute. On Linux, including ARM64 boards, the installer is one line and verifies the SHA-256 checksum of the binary it downloads:
curl -fsSL https://shell.online/install | sh
Then source your workspace and wrap the launch exactly as you would run it locally:
source /opt/ros/lyrical/setup.bash
source ~/ros2_ws/install/setup.bash
shell ros2 launch my_robot_bringup bringup.launch.py
The CLI prints a URL, a generated password, and a QR code. Scan the QR with a phone and the same terminal opens in the browser with the launch output streaming. The encryption page explains what the QR carries: the printed URL holds a random salt fragment, the QR adds the password to that fragment, and URL fragments never reach the relay. The CLI and the browser derive the same AES-256-GCM key locally with PBKDF2-HMAC-SHA256 at 600,000 iterations. Frames are encrypted on the robot before they go anywhere.
The launch runs in the background on the robot. If a colleague is standing next to it with a keyboard, they can take over locally without disconnecting the browser viewers:
shell list
shell attach <session-id>
Ctrl-X then D detaches and leaves the process running. Local input and the resulting output stay visible to everyone in a browser, so nobody is typing into a terminal the other person cannot see.
What a phone can and cannot do to a robot
This is the part to read before you paste the link into a group chat. The URL and password together are a bearer credential. The security model calls it a bearer capability: anyone holding both can open the share, and on an interactive share they can type with the permissions of the wrapped process. On a robot, the wrapped process is usually running as a user that can publish to cmd_vel, restart nodes, or call an e-stop service. A terminal on a robot is a control surface, and the link is the key to it.
Digit 5 solves the physical version of this with hardware: an independent safety controller that acts regardless of what the main software is doing. The equivalent on the terminal side is deciding, before you share, whether anyone on the far end should be able to type at all. For a robot that a support engineer only needs to observe, share the launch read-only:
shell --read-only ros2 launch my_robot_bringup bringup.launch.py
Read-only is enforced at the relay, not in the browser. Per the security page, ordinary browser input and confirmed EOF are rejected by the Worker, and DevTools or handcrafted WebSocket frames cannot turn the link into an interactive session. That matters when the process is a launch file holding a motor controller.
An interactive shell.online link on a robot lets anyone who holds the URL and password send keystrokes with the permissions of the process you wrapped. If the goal is to let someone watch a bringup, a colcon build, or a bag replay, share it read-only. Rotate the password with shell password rotate if a link went further than intended; rotation disconnects current viewers without restarting the process.
One more behavior worth knowing: phone viewers change the terminal grid. The mobile page documents it. A desktop-only session uses a 120 by 36 grid, and the moment a phone connects, everyone switches to an 80 by 24 compatibility grid. Read-only phones count too. Wide output or a TUI will reflow when someone opens the link from a phone.
Network loss, restarts, and links that survive them
Robots lose Wi-Fi. They roam between access points, drive into the metal corner of a warehouse, or reboot when a battery swap goes badly. The reliability page is specific about what happens: the CLI and browser reconnect with bounded backoff, a temporary network failure does not stop the process, and the same share link remains recoverable for 12 hours after its host disconnects. A browser tab left open on your desk reconnects when the robot's compute comes back into range.
A reboot is different, because the process itself is gone. An ordinary share closes when its task exits, and rerunning the launch gives you a new URL and password. For a robot that you want reachable at one stable address across restarts, the CLI has a persistent mode:
shell --persistent /var/lib/robot/shell-state -- ros2 launch my_robot_bringup bringup.launch.py
The state file stores an owner-only host identity, the password, and the encryption material. Rerunning with that same file restores the same URL and password. Put that command in the robot's startup script and the link you saved last week still works after a power cycle. The shell.online source ships the same mechanism in its published Docker image, where a named volume plus restart: unless-stopped keeps one encrypted link across container restarts. Two edges: the persistent flag cannot be combined with the no-e2ee flag, and shell kill stops the process without deleting the identity file, so the next run reuses it.
For a session that should not live all afternoon, such as a bag replay for a bug report, add an earlier deadline:
shell --read-only --auto-close 30m ros2 bag play /data/incident_0915
The auto-close value can only bring the deadline forward. The share still closes the moment the wrapped process exits, whichever comes first.
Letting an agent on the robot hand you the link
If a coding agent runs on the robot's compute, bisecting a launch-file regression after a Humble patch or babysitting a colcon build, it can start the share itself and hand the operator the link without interactive setup, using the JSON mode in the CLI reference:
shell --read-only --json -- colcon build --symlink-install
The first JSON event carries the share URL, the password, the session ID, and flags for read-only and encrypted. The installable agent skill spells out the rules: preserve the full URL including its salt fragment, treat URL and password as a bearer secret, prefer read-only when the operator only needs to monitor, and never create a new session just to refresh a link. On a robot, where a stray keystroke has physical consequences, read-only by default is the right instinct for an agent.
None of this changes where the process runs. The launch, the build, the bag replay all stay on the robot's compute with its own environment sourced. The relay sees connection metadata, encrypted frame sizes, timing, labels, and lifecycle events, and nothing of the terminal content. That is the split we described when we first wrote up a live browser link for any terminal, and a robot is where it earns its keep.
Digit 5 is expected to reach early access in the first half of 2027. Most ROS robots today are not humanoids with a fleet dashboard. They are a compute box, a launch file, and a person who needs to see what it printed, from wherever they are, without a VPN and without handing a control surface to everyone who has the link.
Put a browser link on your robot's terminal
Source your ROS environment, wrap the launch with one command, and open the same terminal from a phone. End-to-end encrypted by default, read-only when you say so, and the process never leaves the robot.
Try shell.online