Eric Bower
·
01 Dec 24
marketing.page.tmpl
1{{template "base" .}}
2
3{{define "title"}}pipe: authenticated pubsub over ssh{{end}}
4
5{{define "meta"}}
6<meta name="description" content="authenticated *nix pipes over ssh" />
7
8<meta property="og:type" content="website">
9<meta property="og:site_name" content="{{.Site.Domain}}">
10<meta property="og:url" content="https://{{.Site.Domain}}">
11<meta property="og:title" content="{{.Site.Domain}}">
12<meta property="og:description" content="pubsub using ssh">
13{{end}}
14
15{{define "attrs"}}class="container-sm"{{end}}
16
17{{define "body"}}
18<header class="flex flex-col items-center gap-2">
19 <div class="flex flex-col">
20 <img src="https://pico.sh/logo.svg" alt="pico logo" width="50" height="50" />
21 <canvas id="canvas" width="50" height="50"></canvas>
22 <hr width="100%" class="m-0" />
23 </div>
24 <h1 class="text-2xl font-bold text-center">Authenticated *nix pipes over ssh</h1>
25</header>
26
27<article class="flex flex-col gap-2">
28 <div>
29 <p>
30 The simplest authenticated pubsub system. Send messages through
31 user-defined topics (aka channels). By default, topics are private to the authenticated
32 ssh user. The default pubsub model is multicast with bidirectional
33 blocking, meaning a publisher (<code>pub</code>) will send its message to all
34 subscribers (<code>sub</code>) for a topic. There can be many publishers
35 and many subscribers on a topic. Further, both <code>pub</code> and
36 <code>sub</code> will wait for at least one event to be sent or received on the topic.
37 </p>
38
39 <h2 class="text-lg">Features</h2>
40
41 <ol>
42 <li>Familiar *nix pipes API</li>
43 <li>Zero-install</li>
44 <li>Authenticated pubsub using ssh</li>
45 <li>Private pubsub by default</li>
46 <li>Public pubsub by topic (opt-in)</li>
47 <li>Multicast (many pubs to many subs)</li>
48 <li>Bidirectional (e.g. chat)</li>
49 <li>Paradigms for connecting to a topic:
50 <ol>
51 <li>Read (<code>sub</code>)</li>
52 <li>Write (<code>pub</code>)</li>
53 <li>Read & Write (<code>pipe</code>)</li>
54 </ol>
55 </li>
56 </ol>
57 </div>
58
59 <div>
60 <h2 class="text-xl">A basic API</h2>
61 <p>Pipe some data into our ssh app and we will send it to anyone listening.</p>
62 <pre>ssh {{.Site.Domain}} sub mykey</pre>
63 <pre>echo "hello world!" | ssh {{.Site.Domain}} pub mykey</pre>
64
65 <details>
66 <summary>Demo</summary>
67 <script
68 src="https://asciinema.org/a/679717.js"
69 id="asciicast-679717"
70 async="true"
71 data-theme="dracula"
72 data-loop="true"
73 data-speed="1.5"
74 data-idle-time-limit="2"
75 ></script>
76 </details>
77 </div>
78
79 <div>
80 <h2 class="text-xl">Simple desktop notifications</h2>
81 <p>Want to quickly receive a notification when a job is done? It can be as simple as:</p>
82 <pre>ssh {{.Site.Domain}} sub notify; notify-send "job done!"</pre>
83 <pre>./longjob.sh; ssh {{.Site.Domain}} pub notify -e</pre>
84 </div>
85
86 <div>
87 <h2 class="text-xl">File sharing</h2>
88 <p>Sometimes you need data exfiltration and all you have is SSH:</p>
89 <pre>cat doc.md | ssh {{.Site.Domain}} pub thedoc</pre>
90 <pre>ssh {{.Site.Domain}} sub thedoc > ./important.md</pre>
91
92 <details>
93 <summary>Demo</summary>
94 <script
95 src="https://asciinema.org/a/679715.js"
96 id="asciicast-679715"
97 async="true"
98 data-theme="dracula"
99 data-loop="true"
100 data-speed="1.5"
101 data-idle-time-limit="2"
102 ></script>
103 </details>
104 </div>
105
106 <div>
107 <h2 class="text-xl">Pipe command output</h2>
108 <p>
109 Send command output through our <code>pipe</code> command. The
110 <code>pipe</code> command is just like <code>pub</code> except it
111 is non-blocking and also acts like a <code>sub</code>. So a client
112 that can read and write to the topic.
113 </p>
114 <pre>ssh {{.Site.Domain}} sub htop</pre>
115 <pre>htop | ssh {{.Site.Domain}} pipe htop</pre>
116
117 <details>
118 <summary>Demo</summary>
119 <script
120 src="https://asciinema.org/a/679712.js"
121 id="asciicast-679712"
122 async="true"
123 data-theme="dracula"
124 data-loop="true"
125 data-speed="1.5"
126 data-idle-time-limit="2"
127 ></script>
128 </details>
129 </div>
130
131 <div>
132 <h2 class="text-xl">Chat</h2>
133 <p>Use our <code>pipe</code> command to have a chat with someone.</p>
134 <pre>ssh {{.Site.Domain}} pipe mychan -p</pre>
135 <p>
136 Now anyone with a <code>pico</code> account can subscribe to this
137 topic using the same command and start typing!
138 </p>
139
140 <details>
141 <summary>Demo</summary>
142 <script
143 src="https://asciinema.org/a/679709.js"
144 id="asciicast-679709"
145 async="true"
146 data-theme="dracula"
147 data-loop="true"
148 data-speed="1.5"
149 data-idle-time-limit="2"
150 ></script>
151 </details>
152 </div>
153
154 <div>
155 <h2 class="text-xl">Pipe reverse shell</h2>
156 <p>If you squint hard enough you can give users interactive access to your shell.</p>
157 <pre>mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | ssh {{.Site.Domain}} pipe myshell > /tmp/f</pre>
158 <pre>ssh {{.Site.Domain}} pipe myshell</pre>
159
160 <details>
161 <summary>Demo</summary>
162 <script
163 src="https://asciinema.org/a/679704.js"
164 id="asciicast-679704"
165 async="true"
166 data-theme="dracula"
167 data-loop="true"
168 data-speed="1.5"
169 data-idle-time-limit="2"
170 ></script>
171 </details>
172 </div>
173
174 <div>
175 <h2 class="text-xl">Simple CI/CD</h2>
176 <p>
177 I'm always looking for easy ways to simplify deploying apps
178 automatically. Having an authenticated, zero-install event system
179 seems handy for this purpose.
180 </p>
181 <pre>while true; do ssh {{.Site.Domain}} sub deploy-app; docker compose pull && docker compose up -d; done</pre>
182 <pre>docker buildx build --push -t myapp .; ssh {{.Site.Domain}} pub deploy-app -e</pre>
183 </div>
184
185 <div>
186 <h2 class="text-xl">Pubsub interactions</h2>
187
188 <h3 class="text-lg">Multiple subs</h3>
189 <p>
190 Have many subscribers, they will all receive the message.
191 </p>
192 <pre>ssh {{.Site.Domain}} sub foobar</pre>
193 <pre>ssh {{.Site.Domain}} sub foobar</pre>
194 <pre>while true; do echo "foobar1"; sleep 1; done | ssh {{.Site.Domain}} pub foobar</pre>
195
196 <details>
197 <summary>Demo</summary>
198 <script
199 src="https://asciinema.org/a/679699.js"
200 id="asciicast-679699"
201 async="true"
202 data-theme="dracula"
203 data-loop="true"
204 data-speed="1.5"
205 data-idle-time-limit="2"
206 ></script>
207 </details>
208
209 <h3 class="text-lg">Multiple pubs</h3>
210 <p>Have many publishers send messages to subscribers.</p>
211 <pre>while true; do echo "foobar1"; sleep 1; done | ssh {{.Site.Domain}} pub foobar</pre>
212 <pre>while true; do echo "foobar2"; sleep 1; done | ssh {{.Site.Domain}} pub foobar</pre>
213 <pre>ssh {{.Site.Domain}} sub foobar</pre>
214
215 <details>
216 <summary>Demo</summary>
217 <script
218 src="https://asciinema.org/a/679698.js"
219 id="asciicast-679698"
220 async="true"
221 data-theme="dracula"
222 data-loop="true"
223 data-speed="1.5"
224 data-idle-time-limit="2"
225 ></script>
226 </details>
227
228 <h3 class="text-lg">Multiple pubs and subs</h3>
229 <p>Have many publishers send messages to many subscribers.</p>
230 <pre>ssh {{.Site.Domain}} sub foobar</pre>
231 <pre>ssh {{.Site.Domain}} sub foobar</pre>
232 <pre>while true; do echo "foobar1"; sleep 1; done | ssh {{.Site.Domain}} pub foobar</pre>
233 <pre>while true; do echo "foobar2"; sleep 1; done | ssh {{.Site.Domain}} pub foobar</pre>
234
235 <details>
236 <summary>Demo</summary>
237 <script
238 src="https://asciinema.org/a/679694.js"
239 id="asciicast-679694"
240 async="true"
241 data-theme="dracula"
242 data-loop="true"
243 data-speed="1.5"
244 data-idle-time-limit="2"
245 ></script>
246 </details>
247 </div>
248
249 <div>
250 <h2 class="text-xl">Send a public message</h2>
251 <pre>echo "hello world!" | ssh {{.Site.Domain}} pub mychan -p</pre>
252 <p>Now anyone with a <code>pico</code> account can subscribe to this topic:</p>
253 <pre>ssh {{.Site.Domain}} sub mychan -p</pre>
254 </div>
255
256 <div>
257 <h2 class="text-xl">Caveats</h2>
258 <p>
259 You must always pipe something into <code>pub</code> or else it will block
260 indefinitely until the process is killed. However, you can provide a
261 flag to send an empty message: <code>pub xyz -e</code>.
262 </p>
263 </div>
264
265 <div>
266 <h2 class="text-xl">Inspiration</h2>
267 <p>
268 A special thanks to <a href="https://patchbay.pub">patchbay.pub</a> for our inspiration.
269 </p>
270 </div>
271
272 <div>
273 <h2 class="text-xl">Latest posts</h2>
274 <div class="flex items-center">
275 <div class="mr font-italic">2024-10-06</div>
276 <div><a href="https://blog.pico.sh/ann-022-pubsub">pipe: our pubsub ssh service</a></div>
277 </div>
278 </div>
279
280 <div class="text-center mb-2">
281 <p>built on our <a href="https://github.com/picosh/pubsub">go pkg</a>.</p>
282 <a href="https://pico.sh/getting-started" class="btn-link inline-block">GET STARTED</a>
283 </div>
284</article>
285
286{{template "marketing-footer" .}}
287{{end}}