Skip to content

Latest commit

 

History

230 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Sproutboat

https://sproutboat.com

Workers-style HTTP handlers on one Linux box you control

Overview

This repository contains the self-hosted Sproutboat platform: the control API, the public edge, the supervisor that runs each deployment under bubblewrap, the dashboard, and install.sh, which provisions all of it on a fresh VPS.

You compile your handler on your own machine. Porffor and Zig turn export default { fetch } into a single linux-x86_64 executable, and what you upload is an artifact: a manifest, one sprout binary, and any binding or asset sidecars. No source, no node_modules, and no bundler config leaves your laptop. The server's job is narrow — verify, store, route, and run immutable artifacts in a sandbox. It never builds anything.

The CLI is a separate repository, baronunread/sproutboat-cli. It is MIT licensed, does the Porffor build, and targets either this platform or the hosted one.

Sproutboat is an experimental proof of concept. It is not production-ready and it is not generally Workers-compatible. Managed hosting is coming; the CLI already works against a hosted control plane, so nothing changes for you when it opens.

Installing

SSH into a fresh Debian/Ubuntu or RHEL-family x86-64 box and run:

curl -fsSL https://raw.githubusercontent.com/baronunread/sproutboat/main/install.sh | sudo bash

That installs the newest released version. Set SB_REF to pin one (SB_REF=v0.2.0) or to track development (SB_REF=main); sbctl update moves to the newest release unless you say otherwise.

sbctl update is safe to run through sudo from a normal operator account. Its root-owned bootstrapper stages the target release outside the active installation and then runs that staged release's installer. This lets an installer fix apply during the same update rather than waiting for another upgrade. The bootstrapper records the installed commit in /etc/sproutboat/update.env and serializes concurrent updates.

Before upgrading, read CHANGELOG.md. The public compatibility policy and release checklist are in docs/versioning.md.

It asks three or four questions — domain, ACME email, admin name — then runs unattended: unprivileged user namespaces, Caddy and bubblewrap, a default-deny firewall, the dashboard build, one admin identity, and the systemd services. Set SB_DOMAIN, SB_ACME_EMAIL and SB_ADMIN to skip the questions.

It pauses once for a single wildcard DNS record, and waits for it to resolve:

Type:  A       Name:  *.example.com   (literally  *  )
Value: <your box's public IPv4>       Proxy: OFF / DNS only

That one record covers control., dashboard., and every <project>.<admin>.example.com deployment. Caddy issues a certificate per hostname over HTTP-01 and TLS-ALPN-01, so you need no DNS API token and no wildcard certificate. The full runbook is in infra/README.md.

Using

bunx sproutboat init hello
bunx sproutboat login --api-url https://control.example.com
bunx sproutboat deploy

A handler is a Cloudflare-style default export:

// hello/src/index.js
export default {
  fetch(request) {
    const name = new URL(request.url).searchParams.get("name");
    return new Response(name ? `${env.GREETING}, ${name}` : env.GREETING);
  },
};

env carries the vars from your sproutboat.jsonc (baked into the artifact), project secrets (AES-256-GCM at rest, never in the binary), and bindings for KV, D1, R2, queues, Durable Objects, cron, analytics and static assets. A KV, D1, R2 or queue store is an account-level resource with a stable id, so its data outlives a redeploy and a second project can bind the same id.

Every deploy is a new content-addressed version. Rollback re-points the route and never rebuilds, and a compile error uploads zero bytes and leaves the live version untouched.

Full documentation is at sproutboat.com/docs. A plain-text copy for agents is at sproutboat.com/llms.txt.

How a deployment runs

The supervisor gives each artifact a loopback port, starts it, waits for it to listen, and restarts it if it exits. It never spawns per request and never recycles: RSS stays flat over 500k requests. A bare handler is about 0.42 MB on disk and 1.5 MB resident.

On Linux every sprout runs under bubblewrap (infra/sandbox/sprout-sandbox.sh): a read-only filesystem holding only its own artifact directory, its own uid, private namespaces, and no network route except loopback. A per-sprout cgroup scope caps memory, CPU and pids.

Every artifact ships bindings.json, so every sprout gets a binding broker beside it on a token-gated loopback port. The broker implements KV, D1, R2, queues, secrets and outbound fetch, which is what keeps the sprout itself free of disk and egress. A handler reaches the outside world only through the broker, which never connects to a private or reserved address (#174). On the systemd install the broker can't reach the network itself, so it hands each request to sproutboat-egress.service, a token-gated loopback service that makes the same checks (#252). An operator allows specific private addresses with SB_EGRESS_ALLOW in /etc/sproutboat/egress.env.

Cron, queue consumers and Durable Object alarms are different from HTTP-only handlers: their active route generation is started by the edge even with no incoming request and remains resident with its broker. The edge notices route snapshot changes without traffic, wakes at most two timed generations at once, and retries a failed timed wake with capped backoff. HTTP-only sprouts remain lazy and are still evicted after the normal idle window. This is a single-node lifecycle guarantee, not a durable scheduler: missed cron ticks during a node outage are not replayed, and queue delivery keeps the broker's existing retry and acknowledgement semantics.

Building

Use Bun, not npm.

bun install
bun run dev:local     # control, edge, dashboard and a GitHub emulator on *.sproutboat.localhost
bun run seed --reset  # demo accounts, projects, resources and traffic
bun test
bun run validate      # typecheck, test, capability harness, report

dev:local serves the stack over local TLS; trust Portless's CA on first run, sign in at https://dashboard.sproutboat.localhost/, and deploy to it with sproutboat login --api-url https://control.sproutboat.localhost. To reset, stop it and remove .local/sproutboat and ~/.config/sproutboat.

Compatibility

The capability harness answers one question: does the installed Porffor run enough real webhook-style handlers to justify the capability profile the platform supports? Compilation alone does not count; behavior has to match Bun's on every probe.

bun run validate rebuilds COMPAT.md, which opens with the compiler version, compile and match counts, median binary size, and a GO/NO-GO decision. Porffor alpha-15 with @sproutboat/toolchain@0.5.0 is currently 32/32 compile, 30/32 match. The two misses are non-ISO Date parsing (#90).

Repository layout

apps/control          control API — auth, projects, artifacts, routes, backups
apps/web              React dashboard (Vite + TanStack Router)
services/edge         public request path, metrics, logs
services/supervisor   per-deployment sprout and broker processes
install.sh, infra/    single-VPS provisioner, systemd units, sandbox launcher
tools/                capability harness and the local dev stack
tests/porffor/        the 32-handler Porffor capability suite
docs/                 artifact, bindings, runtime and self-hosted design notes

Bugs

Please file issues about this code on the issue tracker. Bugs in the CLI belong in sproutboat-cli.

Contributing

Pull requests are welcome, but please file a bug first and reference it from the commit. See git log for the commit message style: a lowercase scope, a short summary in the imperative, and the issue number in parentheses.

bun run validate is what CI runs. Run it before opening a PR.

Legal

Sproutboat is MIT licensed; see LICENSE. It builds on Porffor, uWebSockets, bubblewrap and Caddy, each under its own license.

About

Self-hosted single-VPS platform for locally-compiled JS functions (Porffor)

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages