Skip to content

Latest commit

 

History

History
394 lines (296 loc) · 23.2 KB

File metadata and controls

394 lines (296 loc) · 23.2 KB

Services

This document describes the hosted services and CI/CD infrastructure that support blit. For the system architecture, see ARCHITECTURE.md. editor:local:/src/blit/SERVICES.mdFor development workflow, see CONTRIBUTING.md.

Running as a service

macOS (Homebrew)

brew services start blit server
brew services start blit gateway

Debian / Ubuntu (systemd)

sudo systemctl enable --now blit-server@alice.socket

# Share via WebRTC: create /etc/blit/share-alice.env with
# BLIT_SOCK=/run/blit/alice.sock and BLIT_PASSPHRASE=<secret>, then:
sudo systemctl enable --now blit-share@alice.service

All three blit daemons (server, gateway, share) send READY=1 over $NOTIFY_SOCKET once they're actually serving — server after the IPC listener is bound, gateway after the TCP listener is bound, share after the signaling hub registers the producer. Units in this repo ship with Type=notify so systemctl start returns only after the service is genuinely consumable. The notify implementation is in crates/sd-notify/ and is a no-op when NOTIFY_SOCKET is unset, so the same binaries still work outside systemd.

Multi-remote gateway (blit gateway)

blit gateway can front multiple remote hosts in a single browser UI. Configure remotes in ~/.config/blit/blit.remotes (same file used by blit open):

rabbit = ssh:rabbit
hound = ssh:alice@hound
BLIT_PASSPHRASE=secret blit gateway

To avoid storing the gateway passphrase in plaintext, store an argon2id PHC hash instead:

BLIT_PASSPHRASE=$(blit hash-passphrase secret) blit gateway

Browsers still enter secret; the hash embeds its random salt and argon2 parameters.

SSH remotes are connected via the embedded SSH client (russh) with ssh-agent authentication. blit is auto-installed on remote hosts if missing.

To proxy share: (WebRTC) remotes through the gateway instead of having the browser connect to the hub directly, set BLIT_GATEWAY_WEBRTC=1:

hound = share:mysecret
rabbit = share:anothersecret?hub=wss://custom.hub
BLIT_GATEWAY_WEBRTC=1 BLIT_PASSPHRASE=secret blit gateway

The gateway connects as a WebRTC consumer (using the passphrase-derived channel identity; the hub assigns each connection a unique sessionId so multiple consumers can connect to the same producer concurrently) and re-exposes each destination at WS /d/<name>. The browser never touches WebRTC or the hub. Without BLIT_GATEWAY_WEBRTC=1, share: entries are ignored by the gateway and the browser connects to the hub directly.

Nix

For nix-darwin and NixOS service modules, see nix/README.md.

install.blit.sh

install.blit.sh is an APT repository and binary download site hosted on GitHub Pages. It is rebuilt and deployed on every tagged release (v*). Users interact with it in three ways:

  1. Curl installer — curl -sf https://install.blit.sh | sh fetches the install script (served as index.html), which detects OS/arch, downloads the right tarball from /bin/, and installs it.
  2. PowerShell installer — irm https://install.blit.sh/install.ps1 | iex installs blit on Windows. Downloads the zip from /bin/, extracts blit.exe to %LOCALAPPDATA%\blit\bin, and adds it to the user PATH.
  3. APT repository — Debian/Ubuntu users add it as a signed APT source for apt install blit.
  4. Direct download — tarballs are available at /bin/blit_<version>_<os>_<arch>.tar.gz, Windows zips at /bin/blit_<version>_windows_x86_64.zip.

File hierarchy

install.blit.sh/
  index.html              # install.sh (curl installer served as the landing page)
  install.ps1             # PowerShell installer for Windows
  latest                  # plain-text file containing the current version (e.g. "0.12.0")
  blit.gpg                # GPG public key for APT signature verification
  bin/
    blit_0.12.0_linux_x86_64.tar.gz
    blit_0.12.0_linux-musl_x86_64.tar.gz
    blit_0.12.0_linux_aarch64.tar.gz
    blit_0.12.0_linux-musl_aarch64.tar.gz
    blit_0.12.0_darwin_aarch64.tar.gz
    blit_0.12.0_windows_x86_64.zip
  pool/
    blit_0.12.0_amd64.deb
    blit_0.12.0_arm64.deb
  dists/stable/
    Release               # APT release metadata
    Release.gpg            # detached GPG signature
    InRelease              # clearsigned release
    main/binary-amd64/
      Packages             # APT package index
      Packages.gz
    main/binary-arm64/
      Packages
      Packages.gz

How the install script works

install.sh is a portable POSIX shell script that:

  1. Detects OS (uname -s) and architecture (uname -m), normalizing to linux/darwin and x86_64/aarch64.
  2. On Linux, detects the system libc (musl vs glibc) by checking ldd --version output and /lib/ld-musl-*. Musl systems get linux-musl tarballs.
  3. Fetches /latest from install.blit.sh to get the current version.
  4. Skips if the installed version already matches.
  5. Downloads the tarball from /bin/blit_<version>_<os>_<arch>.tar.gz.
  6. Extracts the tarball (bin/blit single binary) into $BLIT_PREFIX (default /usr/local), escalating with sudo/doas if needed.

Windows installer

install.ps1 is the Windows equivalent:

  1. Fetches /latest to get the current version.
  2. Downloads the zip from /bin/blit_<version>_windows_x86_64.zip.
  3. Extracts blit.exe to $BLIT_INSTALL_DIR (default %LOCALAPPDATA%\blit\bin).
  4. Adds the install directory to the user PATH if not already present.

blit upgrade

blit upgrade is the in-place self-update command. On Unix it fetches install.sh from https://install.blit.sh, writes it to a temp file, and execs sh with BLIT_PREFIX set to the install prefix of the currently running binary. On Windows it fetches install.ps1 and runs it via PowerShell. See crates/cli/src/main.rs.

How the site is built

The apt-repo job in the release workflow assembles the entire site from build artifacts, signs the APT metadata with GPG, and deploys via GitHub Pages:

flowchart TD
    subgraph "Build phase (reusable workflows)"
        BP_X86[build-packages<br>linux-x86_64<br>debs + tarballs]
        BP_ARM[build-packages<br>linux-aarch64<br>debs + tarballs]
        BP_MAC[build-packages<br>macos-aarch64<br>tarballs only]
        BW[build-windows<br>x86_64]
    end

    subgraph "apt-repo job"
        DL[Download all deb + tarball + windows artifacts]
        REPO[Assemble repo/ directory]
        SIGN[GPG-sign Release metadata]
        PAGES[Deploy to GitHub Pages]
        DL --> REPO --> SIGN --> PAGES
    end

    BP_X86 --> DL
    BP_ARM --> DL
    BP_MAC --> DL
    BW --> DL
Loading

hub.blit.sh

hub.blit.sh is the WebRTC signaling relay that enables blit share. It runs on Fly.io and is deployed automatically when code under js/hub/ changes on main.

The hub routes WebRTC signaling messages (offers, answers, ICE candidates) between peers over WebSocket. Channels are identified by ed25519 public keys, and the server verifies NaCl crypto_sign envelopes before relaying — no server-side accounts needed.

For protocol details, deployment instructions, and configuration, see js/hub/README.md.

Architecture

  • Bun runtime with Bun.serve() for HTTP and WebSocket
  • Redis for cross-instance pub/sub and session tracking (sets with TTL)
  • tweetnacl for ed25519 signature verification
  • Stateless — all session state lives in Redis, so instances scale horizontally

Endpoints

Path Purpose
/channel/<pubkey>/<producer|consumer> WebSocket upgrade for signaling
/ice STUN/TURN server config (Cloudflare TURN if configured)
/message Session URL template for client display
/health Liveness check (pings Redis)

blit.sh

blit.sh is the public website, deployed to Vercel. It serves two purposes:

  1. Landing page (/) — marketing page with install instructions, feature overview, and a join form to connect to a shared terminal.
  2. Terminal viewer (/#psk=<secret>) — browser-based terminal that connects to a shared terminal via WebRTC through hub.blit.sh.

The site is an Astro + Solid app built from js/website/. It consumes @blit-sh/core and @blit-sh/solid via workspace aliases and inlines the browser WASM module at build time.

Build and deploy

The website is built as a Nix derivation (websiteDist in nix/packages.nix) which compiles the WASM crate, installs pnpm deps via fetchPnpmDeps, and runs astro build. The deploy-website task in nix/tasks.nix assembles a Vercel prebuilt output directory and deploys via pnpm dlx vercel deploy --prebuilt.

  • Production deploy — pushes to main that touch js/website/**, js/core/**, js/react/**, or crates/browser/** trigger ./bin/deploy-website --prod.
  • Preview deploy — PRs with the same path changes get a preview deploy with the URL posted as a PR comment.

Local usage

./bin/deploy-website          # preview deploy
./bin/deploy-website --prod   # production deploy

Release binaries via Nix

All release binaries are built with Nix, which makes the entire toolchain reproducible and keeps the build definitions small.

On Linux, two variants are shipped:

  • glibc (blit-gnu) — libopus is statically linked; only glibc itself is dynamic. Built with cargo-zigbuild targeting glibc 2.31, so the binary runs on Ubuntu 20.04+, Debian 11+, RHEL 8+, etc. dlopen works natively for GPU acceleration (VA-API, NVENC, Vulkan). This is the default for most Linux systems.
  • musl (blit-musl) — built with the LLVM musl cross toolchain. All dependencies except musl libc are statically linked, producing a single binary. The interpreter is set to the system ld-musl-<arch>.so.1. For Alpine and other musl-based systems.

Both release tarballs contain a single binary at bin/blit.

The blit-gnu derivation in nix/packages.nix builds the glibc binary with static dep overrides and cargo-zigbuild. The blit-release-gnu derivation patches the interpreter to the standard system path via patchelf. The blit-release-musl derivation does the same for musl. The blit-musl build verifies its only NEEDED library is libc.so. install.sh auto-detects the system libc (musl vs glibc) and downloads the right tarball.

On macOS, true static linking isn't practical (Apple doesn't ship static system libraries). Instead, postFixup rewrites any nix-store dylib references to their /usr/lib/ equivalents (libSystem, libc++, libresolv, etc.) using install_name_tool, so the binary runs on stock macOS without Nix installed.

The Rust toolchain is configured with musl targets (x86_64-unknown-linux-musl, aarch64-unknown-linux-musl) in nix/common.nix. The same toolchain also includes wasm32-unknown-unknown for the browser WASM build.

The glibc tarballs on install.blit.sh/bin/ and .deb packages require only system glibc ≥ 2.31. The musl tarballs require only system musl libc. All are single-binary downloads with no external library dependencies (GPU drivers are loaded via dlopen at runtime on glibc systems). macOS and Windows binaries have no required dependencies.

On Windows, Nix isn't available, so the _build-windows.yml reusable workflow uses cargo build --release directly on a Windows runner with the MSVC toolchain. The resulting .exe files link against standard Windows system DLLs (kernel32, ws2_32, etc.) that are always present.

GitHub Actions workflows

Seven workflow files live in .github/workflows/. The three _-prefixed files are reusable workflows called by ci.yml and release.yml:

Workflow Trigger Purpose
ci.yml Push to main, PRs Lint, test, e2e, coverage, verify builds
release.yml v* tag push Verify tag signature, build artifacts, create GitHub Release, publish packages, deploy install site
deploy-hub.yml Push to main (paths: js/hub/**) Deploy signaling hub to Fly.io
deploy-website.yml Push to main (paths: js/website/**, js/core/**, js/react/**, crates/browser/**), PRs Build website via Nix, deploy to Vercel (prod on main, preview on PRs)
publish-demo-image.yml Push to main, v* tag Build and push grab/blit-demo Docker image
_build-packages.yml Called by ci.yml, release.yml Build debs + tarballs (one job per arch, shares Nix cache)
_build-windows.yml Called by ci.yml, release.yml Build Windows release binary

CI (ci.yml)

Runs on every push to main and on every pull request. Build jobs are delegated to reusable workflows (_build-packages.yml, _build-windows.yml) shared with the release pipeline. On Linux, each architecture builds both debs and tarballs in a single job so they share the Nix store cache.

flowchart LR
    PR[Push / PR] --> nix[nix-syntax]
    PR --> lint[lint]
    PR --> test_linux[test<br>linux]
    PR --> test_mac[test<br>macOS]
    PR --> e2e[e2e<br>+ Playwright report]
    PR --> cov[coverage<br>+ PR comment]
    PR --> pkg_x86[build-packages<br>linux-x86_64]
    PR --> pkg_arm[build-packages<br>linux-aarch64]
    PR --> pkg_mac[build-packages<br>macos-aarch64]
    PR --> win[build-windows<br>x86_64]
Loading
Job Runner What it does
nix-syntax ubuntu (4 vCPU) nix-instantiate --parse on all .nix files — catches syntax errors in modules that aren't evaluated by nix flake check
lint ubuntu (4 vCPU) ./bin/lint --check — cargo fmt --check + prettier --check + clippy
test ubuntu (8 vCPU), macOS ./bin/tests — cargo test --workspace
e2e ubuntu (8 vCPU) ./bin/e2e — Playwright against the full stack; uploads report artifact
coverage ubuntu (8 vCPU) ./bin/coverage — runs tests with coverage; uploads HTML report and posts summary on PRs
build-packages ubuntu (8 vCPU) ×2, macOS Verify debs + tarballs build (debs on Linux only, tarballs on all 3 platforms)
build-windows windows (8 vCPU) Verify Windows release build compiles (x86_64)

Release (release.yml)

Triggered by pushing a v* tag. A verify-tag job checks the tag signature via the GitHub API before any builds start — unsigned or unverified tags fail the workflow immediately.

flowchart TD
    TAG["v* tag push"] --> VER[verify-tag<br>Check signature via GitHub API]
    VER --> BP & BW

    subgraph "Build (parallel, reusable workflows)"
        BP[build-packages<br>debs + tarballs per arch]
        BW[build-windows<br>x86_64]
    end

    BP & BW --> REL[release<br>Create GitHub Release<br>with .deb + .tar.gz + .zip]
    BP & BW --> APT[apt-repo<br>Assemble APT repo<br>GPG sign, deploy Pages]

    REL --> PUB_CRATES[publish-crates<br>crates.io]
    REL --> PUB_NPM[publish-npm<br>npm registry]
    REL --> BREW[update-homebrew<br>repository-dispatch to<br>indent-com/homebrew-tap]
Loading
Job Depends on What it does
verify-tag — Checks the tag signature via the GitHub API; fails if unsigned or unverified
build-packages verify-tag Reusable workflow: Nix-builds debs + tarballs per architecture (3 runners, shares Nix cache within each)
build-windows verify-tag Reusable workflow: cargo build --release on Windows, packages .exe files into zips
release build-packages, build-windows Downloads all artifacts, creates a GitHub Release with auto-generated notes
publish-crates release ./bin/publish-crates — publishes workspace crates to crates.io
publish-npm release ./bin/publish-npm-packages — publishes @blit-sh/browser, @blit-sh/core, @blit-sh/react, @blit-sh/solid to npm
update-homebrew release Sends a repository-dispatch event to indent-com/homebrew-tap with the new version
apt-repo build-packages, build-windows Assembles the APT repo directory, GPG-signs metadata, deploys to GitHub Pages

Deploy hub (deploy-hub.yml)

Single job, triggered only when files under js/hub/ change on main:

flowchart LR
    PUSH["Push to main<br>(js/hub/**)"] --> DEPLOY["nix run .#deploy-hub<br>via flyctl"]
Loading

Deploy website (deploy-website.yml)

Builds the website via Nix and deploys to Vercel. Runs on pushes to main and on PRs when relevant paths change. PR deploys post a preview URL as a comment.

flowchart LR
    PUSH["Push to main or PR<br>(js/website/**, js/core/**,<br>js/react/**, crates/browser/**)"] --> BUILD["Nix build<br>websiteDist"] --> DEPLOY["vercel deploy<br>--prebuilt"]
    DEPLOY --> COMMENT["Post preview URL<br>(PRs only)"]
Loading

Publish demo image (publish-demo-image.yml)

Builds and pushes grab/blit-demo to Docker Hub. Runs on pushes to main and on v* tags. Tagged releases get an additional version tag.

flowchart TD
    TRIGGER["Push to main<br>or v* tag"] --> BUILD_AMD[build-push<br>amd64] & BUILD_ARM[build-push<br>arm64]
    BUILD_AMD & BUILD_ARM --> MANIFEST[manifest<br>Create multi-arch manifest<br>and push to Docker Hub]
Loading

Release lifecycle

End-to-end flow from version bump to published artifacts:

sequenceDiagram
    participant Dev as Developer
    participant Rel as bin/prepare-release
    participant Git as Git / GitHub
    participant CI as GitHub Actions

    Dev->>Dev: ./bin/release-prepare 0.12.0
    Dev->>Rel: ./bin/prepare-release 0.12.0
    Rel->>Rel: Validate version consistency across<br>Cargo.toml, package.json, nix/common.nix
    Rel->>Rel: Bump all version files
    Rel->>Rel: cargo test -p blit-server
    Rel->>Git: git commit "release 0.12.0"
    Dev->>Git: Push release/0.12.0 branch<br>Open PR against main
    Dev->>Git: Review and merge PR
    Dev->>Dev: ./bin/release-tag 0.12.0
    Dev->>Git: Signed tag v0.12.0 pushed
    Git->>CI: v* tag triggers release.yml + publish-demo-image.yml
    CI->>CI: verify-tag: check signature via GitHub API

    CI->>CI: Build .deb (amd64, arm64)
    CI->>CI: Build tarballs (linux-x86_64, linux-aarch64, macos-aarch64)
    CI->>CI: Build Windows zips (x86_64)
    CI->>Git: Create GitHub Release with all artifacts
    CI->>CI: Publish crates to crates.io
    CI->>CI: Publish packages to npm
    CI->>CI: Dispatch to indent-com/homebrew-tap
    CI->>CI: Assemble + GPG-sign APT repo
    CI->>CI: Deploy install.blit.sh via GitHub Pages
    CI->>CI: Build + push grab/blit-demo to Docker Hub
Loading

Secrets and authentication

Secret Used by Purpose
GPG_PRIVATE_KEY apt-repo Signs APT Release metadata
HOMEBREW_TAP_TOKEN update-homebrew PAT for cross-repo dispatch to homebrew-tap
FLY_API_TOKEN deploy-hub Fly.io deploy token for blit-hub
DOCKERHUB_USERNAME publish-demo-image Docker Hub credentials
DOCKERHUB_TOKEN publish-demo-image Docker Hub credentials
VERCEL_TOKEN deploy-website Vercel API token
VERCEL_ORG_ID deploy-website Vercel organization ID
VERCEL_PROJECT_ID deploy-website Vercel project ID
CARGO_REGISTRY_TOKEN publish-crates Temporary bootstrap token for new crates

publish-crates normally uses no stored secret. It authenticates to crates.io via OIDC trusted publishing — GitHub mints a short-lived ID token (enabled by the id-token: write permission on the release workflow) and exchanges it for a crates.io upload token. A crate name must exist before its trusted publisher can be configured, so set CARGO_REGISTRY_TOKEN for the first release that creates a crate, configure that crate's trusted publisher for release.yml, then remove the secret. publish-npm uses OIDC as well, with --provenance signing the package for npm.