Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,18 @@ browser loads its JavaScript from that origin, is in the

## Status

Pre-1.0, and honest about it. The local terminal, the login service, the
fleet-wide sessions list and the end-to-end pairing all work. The Cloudflare
relay is built and deployable but has not been through its manual end-to-end
gate against a real account ([docs/RELAY.md](docs/RELAY.md)), so treat it as
ready to try rather than ready to rely on. Known rough edges live in
[docs/FOLLOW-UPS.md](docs/FOLLOW-UPS.md).
Released and in daily use. v0.5.1 is the current release, `brew install
karnstack/tap/flue` gets it, and the whole of it works: the local terminal, the
login service, the fleet-wide sessions list, pairing, and the Cloudflare relay.
The relay has been through its manual end-to-end gate
([docs/RELAY.md](docs/RELAY.md)) against a real account, with a phone on a
different network paired to it and a second machine joined to the same relay.
There is a recording of that run at
[flue.sh/docs/setup](https://flue.sh/docs/setup).

It is 0.x, which means what it usually means: commands, flags and the config
file can still change between releases, and an upgrade may ask something of
you. Known rough edges live in [docs/FOLLOW-UPS.md](docs/FOLLOW-UPS.md).

flue is open source and always free.

Expand Down
1 change: 1 addition & 0 deletions site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"deploy": "wrangler deploy"
},
"dependencies": {
"@karnstack/kino": "^0.12.0",
"@tanstack/react-router": "1.170.17",
"@tanstack/react-start": "1.168.27",
"class-variance-authority": "^0.7.1",
Expand Down
75 changes: 75 additions & 0 deletions site/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 66 additions & 0 deletions site/src/components/walkthrough.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { MuxPlayer } from '@karnstack/kino/mux'
import '@karnstack/kino/styles.css'
import { Clapperboard } from 'lucide-react'

import {
WALKTHROUGH_BLUR,
WALKTHROUGH_PLAYBACK_ID,
WALKTHROUGH_POSTER,
WALKTHROUGH_RUNTIME,
} from '@/lib/site'
import { cn } from '@/lib/utils'

/**
* The setup walkthrough.
*
* Nothing here starts playback, and that is worth saying because kino's
* `autoPlay` prop is right there and using it breaks in a way that takes a
* while to see. kino builds its <mux-video> inside a mount effect and sets
* `autoplay` on the element there, while its teardown calls remove(), which
* detaches an element without pausing it. React runs a mount effect twice in
* development, so the first element is left detached and playing and
* unreachable: a second audio track a beat behind the visible one, which no
* control on the page can stop, because the provider that owned it is gone.
*
* The accent is handed over as `var(--primary)` rather than as a colour. kino
* assigns the prop straight to --kino-accent on its own root, so it resolves
* inside whichever theme the page is wearing and the header's toggle moves the
* scrubber with everything else. A literal would have pinned it to one theme.
*
* `tokens` is absent on purpose: playback is public, so there is nothing to
* sign and no key to hold.
*/
export function Walkthrough({
align = 'left',
className,
}: {
/** Follows the section it lands in, the same way InstallBlock does. */
align?: 'left' | 'center'
className?: string
}) {
return (
<figure className={className}>
<div className="relative aspect-video overflow-hidden rounded-xl bg-zinc-950 ring-1 ring-zinc-950/10 dark:ring-white/10">
<MuxPlayer
playbackId={WALKTHROUGH_PLAYBACK_ID}
poster={WALKTHROUGH_POSTER}
placeholder={WALKTHROUGH_BLUR}
accentColor="var(--primary)"
/* kino's glass sits inside a corner this site sets. One step in from
the panel's own, which is what nesting two curves asks for. */
theme={{ '--kino-radius': 'var(--radius-lg)' }}
/>
</div>
<figcaption
className={cn(
'mt-4 flex items-start gap-3 font-mono text-xs tabular-nums text-muted-foreground',
align === 'center' ? 'justify-center text-center' : 'text-left',
)}
>
<Clapperboard className="size-3.5 h-lh shrink-0" aria-hidden="true" />
{WALKTHROUGH_RUNTIME}. Install, one relay in your own Cloudflare account, a phone paired from
a QR code, then a second machine.
</figcaption>
</figure>
)
}
33 changes: 33 additions & 0 deletions site/src/lib/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,36 @@ export const X_URL = 'https://x.com/gyankarn'

export const INSTALL_CMD = 'curl -fsSL https://flue.sh/install.sh | sh'
export const BREW_CMD = 'brew install karnstack/tap/flue'

/**
* The setup walkthrough, on Mux with public playback.
*
* There is no signing key here and nothing to mint at runtime. A playback id
* is the whole of what it takes to watch this, and it is not a secret.
*/
export const WALKTHROUGH_PLAYBACK_ID = 'vnrDD86c33LvJ7Ftv6uIWlEDTGH6dxqnE3cH6e02pZ94'

/** How long it runs. Written in the caption, and read out in the button label. */
export const WALKTHROUGH_RUNTIME = '8:51'

/**
* One frame, named once and used twice: the closed card paints it, and the
* player is handed the same URL as its poster when it opens. Naming it twice
* is what makes the swap seamless, because the second request is a cache hit
* rather than a fresh fetch across an empty rectangle.
*
* 5:30 is the second worth choosing. It is the Ubuntu machine taking the
* install line with the sessions page already up behind it, which is the most
* legible frame in the run and one of the few that says what the whole thing
* is about. Several of the later ones show a relay secret, so they are out.
*/
export const WALKTHROUGH_POSTER = `https://image.mux.com/${WALKTHROUGH_PLAYBACK_ID}/thumbnail.webp?time=330&width=1200`

/**
* That same frame at 24px across, written out here at 263 bytes so the card
* has something to paint before anything is fetched. kino lays it under the
* poster the same way once the player opens, which is why both halves are
* given it.
*/
export const WALKTHROUGH_BLUR =
'data:image/webp;base64,UklGRqwAAABXRUJQVlA4IKAAAAAwBACdASoYAA4APlEgjUQjoiEYBAA4BQSzAFiNqALX4WRJCDRqDujU6YwA/v8rl6RU7q8kvuFZDpsXENrifU0Ahz/kXdneUSBeBVkGphdpCdwmD5sUyoP+dwh+2GmbntH5//OW/GnmdJ0Flr88cz3OPlY3GzJprjYUdTTcvQwS8I3YClHd8aXjjLMDn/p/IzRA7SwbBX3zW5/NZT35QAAA'
14 changes: 14 additions & 0 deletions site/src/routes/docs.setup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createFileRoute } from '@tanstack/react-router'

import { Code, DocPage, Lead, Link, Note, P, Section, Shell, Step, Steps } from '@/components/doc-page'
import { Walkthrough } from '@/components/walkthrough'
import { docTitle, findDoc } from '@/lib/docs'

const DOC = findDoc('setup')!
Expand All @@ -15,6 +16,19 @@ export const Route = createFileRoute('/docs/setup')({
function Setup() {
return (
<DocPage slug="setup" title={DOC.title} blurb={DOC.blurb}>
{/* First, before a single command. Somebody who opened this page has
already decided to install flue, and the thing they want to know
before they start is how long it takes and what it looks like when
it works. The steps below answer neither, and eight minutes of
somebody doing it answers both. */}
<Section title="The whole thing, on camera">
<Lead>
Every step below, run on two machines: a Mac from nothing, then an Ubuntu box joining the
same relay.
</Lead>
<Walkthrough className="max-w-[68ch]" />
</Section>

<Section title="The shape to aim for">
<Lead>
One relay. Every machine joined to it. Every device paired once. Everything below is a
Expand Down
19 changes: 17 additions & 2 deletions site/src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SwitcherWindow } from '@/components/mock/switcher'
import { MockTerminal, ok, output, prompt } from '@/components/mock/terminal'
import { SiteFooter } from '@/components/site-footer'
import { SiteHeader } from '@/components/site-header'
import { Walkthrough } from '@/components/walkthrough'
import { GithubMark } from '@/components/wordmark'
import { BREW_CMD, INSTALL_CMD, REPO_URL } from '@/lib/site'

Expand Down Expand Up @@ -335,8 +336,7 @@ function Remote() {
<p className="mt-4 max-w-[56ch] text-base/7 text-pretty text-muted-foreground sm:text-sm/6">
Everything crossing it is end-to-end encrypted with Noise IK. Your browser pins the
daemon&rsquo;s key when it pairs, so the Worker forwards ciphertext it holds no key
for. The relay is new: it works, but it has not been through its release gate, so
treat it as ready to try rather than ready to rely on.
for. It is a 0.x release, so commands and config can still change between versions.
</p>
<a
href="/docs/relay"
Expand Down Expand Up @@ -413,6 +413,16 @@ function Connector() {

/* -------------------------------------------------------------- install --- */

/**
* The closer, and the one place on the page a recording belongs.
*
* Every other window here is drawn: the fleet, the switcher, the relay
* transcript. Drawings argue well and prove nothing, and the objection at the
* moment somebody is about to pipe a script into a shell is not "how does it
* work", it is "does this actually work". So the walkthrough sits between the
* claim and the command, where that question is live, rather than in the hero
* where nobody has asked it yet and nine minutes is a wall.
*/
function Install() {
return (
<section className="py-20 sm:py-24">
Expand All @@ -432,6 +442,11 @@ function Install() {
</a>
.
</p>
{/* Full width, the way the switcher above sits full width in a
section that is otherwise columns. The caption is centred with
the rest of this section rather than left aligned, since it is
the only line here that would have read off axis. */}
<Walkthrough align="center" className="mt-10 w-full" />
<div className="w-full">
<InstallBlock align="center" />
</div>
Expand Down