https://github.com/sgbalogh/s2.term
Demo multi-player pseudo-terminal over S2 streams
https://github.com/sgbalogh/s2.term
pty rust s2 typescript xterm-js
Last synced: 3 months ago
JSON representation
Demo multi-player pseudo-terminal over S2 streams
- Host: GitHub
- URL: https://github.com/sgbalogh/s2.term
- Owner: sgbalogh
- Created: 2025-06-06T21:48:56.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2025-06-27T16:41:22.000Z (4 months ago)
- Last Synced: 2025-06-27T17:41:04.396Z (4 months ago)
- Topics: pty, rust, s2, typescript, xterm-js
- Language: TypeScript
- Homepage: https://s2.dev/blog/s2-term
- Size: 2.8 MB
- Stars: 3
- Watchers: 0
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# s2.term
Demo of a durable, log-structured [pseudo-terminal](https://en.wikipedia.org/wiki/Pseudoterminal) implemented over [S2](https://s2.dev) streams.
(See also the [blog post](https://s2.dev/blog/s2-term) about this.)

## About
This repo is meant to serve as a proof-of-concept. Please use at your own risk!
It's kinda like a remote shell, e.g. SSH, but:
- Multiplayer, including from browsers. Useful for pair-programming.
- All terminal updates are durable on object-storage before delivery.
- All updates can be replayed ([example](https://www.youtube.com/watch?v=huyhEe5CLcU)); old state can be [periodically trimmed](https://s2.dev/docs/stream#retention).
- No servers necessary, all I/O happens by reading or appending to the S2 API.
- Configurable with [ACLs](https://s2.dev/docs/access-control).
- For example, can configure roles to grant read-only access to terminal viewers, and read-write to others.## Organization
There are two components in this repo currently:
- An [xterm.js](https://xtermjs.org/) based frontend (`ts-frontend`), which can be run locally, or deployed on any web server.
- This interacts with S2 streams via the [S2 Typescript SDK](https://github.com/s2-streamstore/s2-sdk-typescript).
- A Rust binary that acts as PTY (`rust-pty-host`), which will run on the remote host where you want a shell.
- This is built with [pty_process](https://docs.rs/pty-process/latest/pty_process/) crate, and the [S2 Rust SDK](https://github.com/s2-streamstore/s2-sdk-rust).## Running it
### S2 Setup
First, head to [S2.dev](https://s2.dev/) and create an account and token if you haven't already.
> [!IMPORTANT]
> If you plan on sharing access to your terminal with others, make sure to create dedicated (and limited) tokens [with appropriate scopes](https://s2.dev/docs/access-control). These can be revoked at anytime via the [S2 dashboard](https://s2.dev/dashboard).#### Organization
Each terminal "session" requires two S2 streams. This setup assumes an organization like the following:
```bash
s2://${MY_BASIN}/sessions/${MY_SESSION}/term_input
s2://${MY_BASIN}/sessions/${MY_SESSION}/term_output
```For example, my basin looks like this:
```console
% s2 ls s2term-00001
s2://s2term-00001/sessions/0001/term_input 2025-06-06T22:03:22Z
s2://s2term-00001/sessions/0001/term_output 2025-06-06T22:03:26Z
s2://s2term-00001/sessions/0002/term_input 2025-06-06T22:33:26Z
s2://s2term-00001/sessions/0002/term_output 2025-06-06T22:33:31Z
s2://s2term-00001/sessions/0003/term_input 2025-06-06T22:51:30Z
s2://s2term-00001/sessions/0003/term_output 2025-06-06T22:51:34Z
s2://s2term-00001/sessions/0004/term_input 2025-06-09T21:37:57Z
s2://s2term-00001/sessions/0004/term_output 2025-06-09T21:39:00Z
```#### Create necessary streams
This can be done using the [S2 CLI](https://s2.dev/docs/quickstart).
```bash
export MY_ACCESS_TOKEN=""
export MY_BASIN="s2term-00001"
export MY_SESSION="0001"# input stream
s2 create-stream \
s2://${MY_BASIN}/sessions/${MY_SESSION}/term_input \
--storage-class express \
--timestamping-mode client-require# output stream
s2 create-stream \
s2://${MY_BASIN}/sessions/${MY_SESSION}/term_output \
--storage-class express \
--timestamping-mode client-require
```### Starting the Rust PTY
Make sure you [have Rust installed](https://rustup.rs/).
Start the process with `cargo`. In the example below, we will manage a PTY (over a `zsh` shell, though you can use any process you like) for a single session.
```bash
export S2_ACCESS_TOKEN=""
export RUST_LOG=info
cargo run --release -- \
"${MY_BASIN}" \
"${MY_SESSION}" \
--process zsh
```You can toggle trace-level logs with `RUST_LOG=info,s2term_pty_host=trace`.
#### Running the PTY via Docker
Alternatively, run the PTY via a Docker container for some additional security.
```bash
docker build -t s2term-pty-host .
docker run \
-e "S2_ACCESS_TOKEN=${S2_ACCESS_TOKEN}" \
s2term-pty-host \
"${MY_BASIN}" \
"${MY_SESSION}" \
--process zsh
```### Starting the frontend
Install [Node.js](https://nodejs.org/en/download).
Then you can install the frontend with:
```bash
cd ./ts-frontend
npm install
```... and run a local dev server with
```bash
npm run dev
```You should be able to see the site running at `http://localhost:3000`.
By default, the xterm div will start by interactively prompting for basin, session, and token. These can be provided also via query params, e.g.:
```
http://localhost:3000/?basin=s2term-00001&session=0001&token=MY_TOKEN
```Similarly, some other query params:
- `since=EPOCH_MS`
- If an epoch timestamp in ms is provided, the session will attempt to replay from messages starting at that moment.
- `speedup=MULTIPLE`
- In combination with a `since` timestamp, this factor controls how long to wait between records when replaying historical messages. E.g. `speedup=60` would advance time at a rate of 1 min per second.