pipe

PubSub without the infrastructure.


Stream data between machines using the SSH key you already have -- no API keys, no SDKs, no setup.

1echo "hello world" | ssh pipe.pico.sh pub mykey

That's it. Private and authenticated by default.

pipe brings the simplicity of *nix pipes to the network. If you know how to use |, you already know how to use pipe.

Features #

Feature What it means for you
Works on any machine No installation, no dependencies, just SSH
SSH key is your credential No API keys to manage or rotate
Private by default Topics are only accessible to you unless you opt-in
Public topics Share with specific users or make fully public
Primitives that compose Read (sub), write (pub), or both (pipe)
Multicast built-in Many publishers, many subscribers, just works

Examples #

A basic API #

Pipe some data into our ssh app and we will send it to anyone listening.

1ssh pipe.pico.sh sub mykey
1echo "hello world!" | ssh pipe.pico.sh pub mykey

Simple desktop notifications #

Want to quickly receive a notification when a job is done? It can be as simple as:

1ssh pipe.pico.sh sub notify; notify-send "job done!"
1./longjob.sh; ssh pipe.pico.sh pub notify -e

File sharing #

Sometimes you need data exfiltration and all you have is SSH:

1cat doc.md | ssh pipe.pico.sh pub thedoc
1ssh pipe.pico.sh sub thedoc > ./important.md

Pipe command output #

Send command output through our pipe command. The pipe command is just like pub except it is non-blocking and also acts like a sub. So a client that can read and write to the topic.

1ssh pipe.pico.sh sub htop
1htop | ssh pipe.pico.sh pipe htop

Chat #

Use our pipe command to have a chat with someone.

1ssh pipe.pico.sh pipe mychan -p

Now anyone with a pico account can subscribe to this topic using the same command and start typing!

Pipe reverse shell #

If you squint hard enough you can give users interactive access to your shell.

1mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | ssh pipe.pico.sh pipe myshell > /tmp/f
1ssh pipe.pico.sh pipe myshell

Simple CI/CD #

Having an authenticated, zero-install event system is handy for deploying apps automatically.

1while true; do ssh pipe.pico.sh sub deploy-app; docker compose pull && docker compose up -d; done
1docker buildx build --push -t myapp .; ssh pipe.pico.sh pub deploy-app -e

Send a public message #

1echo "hello world!" | ssh pipe.pico.sh pub mychan -p

Now anyone with a pico account can subscribe to this topic:

1ssh pipe.pico.sh sub mychan -p

Pubsub interactions #

Multiple subs #

Have many subscribers, they will all receive the message.

1ssh pipe.pico.sh sub foobar
1ssh pipe.pico.sh sub foobar
1while true; do echo "foobar1"; sleep 1; done | ssh pipe.pico.sh pub foobar

Multiple pubs #

Have many publishers send messages to subscribers.

1while true; do echo "foobar1"; sleep 1; done | ssh pipe.pico.sh pub foobar
1while true; do echo "foobar2"; sleep 1; done | ssh pipe.pico.sh pub foobar
1ssh pipe.pico.sh sub foobar

Multiple pubs and subs #

Have many publishers send messages to many subscribers.

1ssh pipe.pico.sh sub foobar
1ssh pipe.pico.sh sub foobar
1while true; do echo "foobar1"; sleep 1; done | ssh pipe.pico.sh pub foobar
1while true; do echo "foobar2"; sleep 1; done | ssh pipe.pico.sh pub foobar

Topic names #

All topics are converted into the following format: {user}/{topic}. Depending on how the pub is created, it changes how to access the topic. These rules are created in an effort to make is ergonomic for multiple use cases.

  • pub {topic} -> owner can access with sub {topic} or sub {owner}/{topic}
  • pub -a {other} {topic} -> other pico user must access with sub {owner}/{topic}
  • pub -p {topic} -> anyone can access with sub -p {topic}

Web Interface #

Now what if you don't have a terminal available? Not a problem! Pipe has a web component that works side by side, pipe-web. For example, let's start a notification sub through the terminal like so (note: p for public, so anyone can send us a notification):

1ssh pipe.pico.sh sub -p -k notifications | xargs -I{} -L1 osascript -e 'display notification "{}" with title "Pipe Notification"'

We can send a POST to the pipe-web service to send a message onto that topic like so:

1echo "Hello world" | curl -X POST https://pipe.pico.sh/topic/notifications --data-binary @-

And a notification will pop up! Now it's important to note that this is risky, anyone can use a "public" topic (on both the terminal or pipe-web). We can make this less risky by starting the notifications topic with an access list set. And if we want pipe-web to access it, we need to provide "pico" to the access list setting:

1ssh pipe.pico.sh sub -a pico -p -k notifications | xargs -I{} -L1 osascript -e 'display notification "{}" with title "Pipe Notification"'

Now, only pipe-web and yourself are able to access this public topic.

pipe-web comes with a few caveats, namely all topics need to be public for it to work. You can set an access list on the topic, but pipe-web is an unauthenticated service. Therefore, anyone can send a post request to the process.

WebSockets #

Just like you can use pipe-web for GET and POST requests, you can also use WebSockets to get a bi-directional stream to a topic. This functionality is most closely related to the pipe CLI option. You can try this functionality here. The API is simple and documented below. This example terminal also accepts query params like the following:

name type data type description
topic optional boolean The topic to connect to
replay optional boolean Whether or not to replay message
binary optional string Connect the client as binary
autoConnect optional string Autoconnect once the page is loaded

Open a pipe #

GET /pipe/:topic open a pipe to a topic (with upgrade)

Parameters #

name type data type description
topic required string topic name to subscribe to

Query Parameters #

name type data type description
status optional boolean Receive pipe status messages
replay optional boolean Whether or not to replay message
binary optional string Connect the client as binary
access optional string Comma separated list of permissible accessors

Responses #

http code content-type response
101 N/A Switching Protocols

Example websocat #

1websocat wss://pipe.pico.sh/pipe/test

Subscribe to a topic #

GET /topic/:topic subscribe to a topic

Parameters #

name type data type description
topic required string topic name to subscribe to

Query Parameters #

name type data type description
persist optional boolean Persist the subscription after the publisher closes
block optional boolean Block writes until a subscriber is available (default true)
access optional string Comma separated list of permissible accessors
mime optional string Content type to return to the client

Responses #

http code content-type response
200 text/plain;charset=UTF-8 or mime query parameter Subscription data. Will hang until a pub occurs

Example cURL #

1curl -vvv https://pipe.pico.sh/topic/test?persist=true

Publish to a topic #

POST /topic/:topic publish to a topic

Parameters #

name type data type description
topic required string topic name to subscribe to

Query Parameters #

name type data type description
access optional string Comma separated list of permissible accessors

Responses #

http code content-type response
200 No content returned

Example cURL #

1curl -vvvv https://pipe.pico.sh/topic/test -d "hello"

Subscribe to a topic (pubsub) #

GET /pubsub/:topic subscribe to a topic

Parameters #

name type data type description
topic required string topic name to subscribe to

Query Parameters #

name type data type description
persist optional boolean Persist the subscription after the publisher closes
block optional boolean Block writes until a subscriber is available (default true)
access optional string Comma separated list of permissible accessors
mime optional string Content type to return to the client

Responses #

http code content-type response
200 text/plain;charset=UTF-8 or mime query parameter Subscription data. Will hang until a pub occurs

Example cURL #

1curl -vvv https://pipe.pico.sh/pubsub/test?persist=true

Publish to a topic (pubsub) #

POST /pubsub/:topic publish to a topic

Parameters #

name type data type description
topic required string topic name to subscribe to

Query Parameters #

name type data type description
access optional string Comma separated list of permissible accessors

Responses #

http code content-type response
200 No content returned

Example cURL #

1curl -vvvv https://pipe.pico.sh/pubsub/test -d "hello"

pipemgr #

pipemgr is a docker image that will listen for logs from other running containers and pipe their logs through a topic called container-drain.

 1services:
 2  pipemgr:
 3    build:
 4      context: .
 5    image: ghcr.io/picosh/pipemgr:latest
 6    restart: always
 7    volumes:
 8      - /var/run/docker.sock:/var/run/docker.sock:ro
 9      - $HOME/.ssh/id_ed25519:/key:ro
10    healthcheck:
11      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
12      interval: 2s
13      timeout: 5s
14      retries: 5
15      start_period: 1s
16  httpbin:
17    image: kennethreitz/httpbin
18    command: gunicorn -b 0.0.0.0:3000 httpbin:app -k gevent --access-logfile -
19    ports:
20      - 3000:3000
21    labels:
22      pipemgr.enable: true
23      # filter log lines with:
24      # pipemgr.filter: "GET.+(404)"
25    depends_on:
26      pipemgr:
27        condition: service_healthy

Caveats #

You must always pipe something into pub or else it will block indefinitely until the process is killed. However, you can provide a flag to send an empty message: pub topic -e.

Inspiration #

A special thanks to patchbay.pub for our inspiration.

Latest posts #

2024-10-06 pipe: our pubsub ssh service


Create an account using only your SSH key.

Get Started
<< PREV
Tuns
NEXT >>
Prose
Built by pico.sh LLC
206 E Huron St, Ann Arbor MI 48104